欢迎访问

这是我的个人博客,用于记录学习、技术实践与独立思考。内容涵盖计算机科学、开源软件,以及对历史与人文问题的探索性思考。所有观点仅代表个人理解,欢迎理性讨论与交流。

个人 VPS 自部署的长期主义:干净与安全

我开始认真折腾个人 self-host,大概已经有五六年了。严格意义上开始管理我自己的 VPS 的时间点,应该还可以往前追溯。最初的动机其实很简单:有些服务我每天都在用,比如密码管理器、音乐播放、笔记同步、个人网站、代码仓库等等。既然如此,为什么不干脆自己部署?这样一来数据在自己手里,也不用被各种平台订阅和涨价牵着走。 这几年一路走下来,说实话,self-host 并不省心。我要维护系统、备份数据、关心安全、偶尔还要救火。但它给我的回报也很明确: 数据完全掌控在自己手里 不再被订阅价格牵着走 对系统、网络和安全的理解明显更深 现在我一台服务器跑着十几个服务,成本只是一台服务器的钱。在这两年取消了不少第三方平台的订阅后,算下来,整体成本可能反而更低。对我来说,这种“复杂但可控”的状态,反而比依赖一堆外部服务更安心。当然不这适合所有人,我感觉有:有一定技术背景的人、喜欢折腾的人以及愿意为稳定与掌控付出时间的人,这三者合一就适合这样。 到现在,我并不觉得自己成了什么“专家”,但有到现在两件事的感受越来越强烈:安全性以及服务器环境的整洁程度,这也是这篇文章的两个主线。这两个问题,几乎决定了你这台服务器上的各类服务能不能长期稳定地用下去。然后就是需要迁移的时候,比如换了一个更便宜或者更好的 VPS 供应商,这个时候的工作量(我自己就换了好几次,知道真的换不动了才稳定下来)。 草莽阶段 最开始的时候,我的做法非常朴素。我会直接在宿主机上安装服务,然后安装 Nginx 或者 Apache2(httpd)。比如用 Nginx 做反向代理,Nginx 这类 Web 服务器 的好处是配置文件可以拆开,每个站点一份,用域名来区分各个服务而不是端口号,看起来井井有条。那时候我觉得这套方案挺优雅的。 但随着服务慢慢变多,问题开始一点点浮现。有的服务需要特定版本的依赖,我很难在同一个环境安装不同版本的各种依赖库或者数据库。而有的会在系统里留下各种临时文件;配置文件散落在 /etc、数据放在 /srv,日志又在 /var。一开始你还能记得“这是哪个服务的”,时间一长就完全混在一起了。 这期间,当我想删除一个不用的服务时,我已经不敢确定自己删得干不干净。特别是有的服务提供“一键安装脚本”,安装的时候一时畅快,等到了你想摆脱它的时候,你会发现当初的脚本不知道做了什么改动,如何复原都毫无头绪。你很难确认还有没有残留的配置、后台进程,甚至不知道删错东西会不会影响别的服务。这种不确定感,本身就是一个风险,对于强迫症来说,每每想起简直是如鲠在喉。 容器化阶段 后来我把几乎所有服务都迁移到了 Docker 里。而且,到现在越来越倾向于将所有能用的到的服务或者工具软件容器化,再去部署。有些不提供标准的容器化部署方案的,但又很想用的服务,我会直接自己进行容器化包装,形成自己的容器化的解决方案。 真正改变体验的不是 Docker 本身,而是 Docker Compose。容器这东西,本质上就不应该被当成“长期维护对象”,它随时都可以被删掉、重建,你需要意识到这个。早期的时候,我觉得 Docker Compose 配置文件麻烦,为什么不用命令直接创建一个容器,然后让这个容器长期运行。后来等我要更新,或者这个服务依赖多个底层的其他容器的时候,问题就逐渐浮现出来。如果当初将对服务的部署写下来,写到一个配置文件中,然后每次就执行这个配置文件就能完美复现原先的部署,不需要自己额外配置什么 Bridge 网络,或者什么 hostname,亦或者是重启策略。这个节省了大量的时间和精力,并且配置的时候出错的可能性,也大大降低了。 到现在,我能够总结出:真正需要被长期维护的,只有三样东西: 服务配置(包括版本,网络,存储等) 数据(主要是 Volumes 和其他映射的目录) 部署配置(Docker Compose 的各类 Yml 配置文件) 不管是重装系统,还是换一台机器,只要这三者还在,这些服务就能在这个新的地方完美地克隆。而且,宿主机本身终于变得“干净”了。各个目录下不再到处是你几年之前装过、现在已经忘记用途的东西。所以,在这个阶段,我单独创建了一个 git 仓库来收集和管理所有我用到的服务的对应的 Docker Compose 配置文件,一年下来,我感觉这样是经得起各类场景的考验的。 一个服务一个数据库 这个是一个值得一提的点。很长一段时间里,我的所有服务共用一个数据库实例。这在逻辑上看起来非常合理:资源省、结构简单、管理集中。但真正长期维护下来,你会发现这其实是一个非常不友好的设计。 每多一个服务,就多一套用户、权限、密码;想迁移一个服务,就得小心翼翼地把数据库剥离出来;数据库版本一旦升级,所有服务都要跟着承担风险。对于不熟悉某个数据库的人来说,管理权限和用户是一件头疼的东西,你需要通过命令行或者什么数据库管理软件,仔细地区分和妥善保管各个服务的用户以及他们的密码。 后来我干脆反过来做了一件事:**每个服务,单独一个数据库实例。**也就是,每个 Docker Compose 中独立地会有一个数据库的 service。长期观察下来,对个人 self-host 来说,这并没有想象中那么“浪费”。一个数据库容器通常只占一两百兆内存,在一台 4c8g 的 VPS,甚至 2c4g, 或家用服务器上,是可以容忍的。 ...

2026年1月2日

GpgFrontend的前世今生:我与一个开源加密工具的成长之路

缘起:偶遇 GnuPG(GPG) 我在 2021 年 4 月左右第一次接触 GPG。那时我还只是个对计算机世界充满好奇的学生,对密码学的理解也仅停留在“密码就是加密字符串”这样的表层概念。但当我第一次读到非对称加密的原理——一个公钥可以让所有人加密信息,而只有私钥拥有者才能解密——我被深深震撼了。这种“数学意义上的信任”让我着迷。它优雅、严谨、不可逆。那一刻,我意识到,这种技术的美感并不亚于任何艺术创作。于是我开始学习 GPG 命令行的各种操作,从密钥生成到签名验证,反复实验,琢磨其中的逻辑。 但与此同时,我也发现了问题:**命令行的门槛实在太高。**特别是有的时候,想快点做一些简单操作却要输入一串命令,让我感觉“不爽”。对非技术用户来说,GPG 的命令行是一片晦涩的荒原,几乎挡住了所有想尝试的人。我开始想,如果能为 GPG 做一个简单易用的图形界面,让它像一台看得见摸得着的“密码机机”那样可视化与便捷;如果每个人都可以简单地拥有者与一台耐用且小巧的“密码机”,也许会有更多人愿意尝试。这个想法当时看起来微不足道,但正是它,开启了我后来整整数年的技术旅程。 “密码机”让人立刻联想到某种坚固、可靠的小设备,就像早期的机械密码机(如 Enigma)那样充满仪式感。它承载的不仅是技术,更是一种对安全的信念。这种机器功能不求繁多,却必须精准可靠。用户按下一个按钮,期望它执行的动作必须可预期且安全。这种对“可控性”的追求,就是“密码机”的灵魂。 我开始寻找现成的解决方案。那个时候已经有一些 GUI 工具,比如 Kleopatra。但它们的很多都界面复杂、功能太多,甚至还引入了 X.509 证书体系,这在当时一下子劝退了我。我希望的是一个更轻、更纯粹的东西。后来我发现了一个叫 gpg4usb 的项目,它非常接近我理想中的形态——轻量、可携带、跨平台。然而遗憾的是,它早已停止更新,只支持 GPG 1.4 版本。那意味着它与现代系统的兼容性问题几乎无法回避,也无法支持子密钥机制。 我开始琢磨:**能不能在它的基础上重写一部分,让它重新焕发生命?**在 5 月份左右,我花了大约半个月的时间,在宿舍里读它的源码,摸索 GPG 的接口调用方式,研究 Qt 框架与跨平台兼容。那是一个孤独却极其充实的过程。每当我让一个新功能成功运行时,心里的成就感都难以言喻。当我终于让它在 GPG2 环境下运行起来,并修复了不少原有的 Bug 后,我决定给这个焕然一新的项目一个新名字:GpgFrontend——GPG 的图形前端,也是我人生中的第一个完整开源项目。 亮相:从宿舍代码到 GitHub 上的光点 我将第一个版本上传到 GitHub 后,并没有期待太多关注。毕竟这是个极小众的领域,GPG 本身就不是大众常用的东西。 但几天后,我收到了第一个 issue。那是一位用户,提到程序在某个操作系统上无法运行。他不仅提供了日志,还提出了修改建议。那一刻,我第一次感受到某种力量,愿意继续为这一个项目投入时间。 随后,越来越多的人开始使用 GpgFrontend。他们提出问题、建议、甚至翻译。每一个反馈都让我意识到,这个项目不再只是我个人的“技术玩具”,而是真正有用的工具。从那以后,我对“开源”这个概念的理解有了根本改变:它不只是代码共享,更是一个不断反馈、持续演化的生态。 在后续的 2-3 年,我逐渐明白,GpgFrontend 不可能也不需要成为“大而全”的工具。它的使命很清晰:帮助用户更方便地执行加密、解密、签名和验证操作。它的价值,不在于功能数量,而在于易用性和可靠性。最终到现在,我清晰地把它定义成一个“轻量级、安全的密码机”,就像我当初刚刚接触 GPG 的时候渴望得的一样。 对熟悉命令行的老用户,它是一个节省时间的辅助工具;对初学者,它是一座通向密码世界的桥梁。所以在 UI 设计上,我尽量保持简洁,让所有核心操作一目了然;在底层实现上,我让每个动作都直接对应 GPG 原生命令,确保稳定与兼容。后来我意识到,这种“小而专”的定位,反而成了 GpgFrontend 最独特的竞争力。当别的项目追求“更多功能”时,我选择让它保持“更纯粹的专注”。 演进:把项目当成技术的试验田 随着版本的推进,GpgFrontend 成了我学习新技术的练兵场。我不断尝试把在工作和学习中掌握的新概念融入项目中。我研究 Chromium 的多线程架构,把线程调度与任务隔离机制引入 GpgFrontend,使其在执行加密时界面不再卡顿,而且尽可能减少竞态和一些多线程经常遇到的 BUG;我为它设计了插件系统和 SDK 接口,让第三方能在运行时动态扩展功能;我实现了优化了构建脚本,引入了 CI/CD,让不同平台的打包流程更自动化、更可靠。另外,我也通过这个项目的跨平台的支持,学习了 Windows、macOS、Linux 下的各种实现差异,了解了在不同的操作系统和发布平台下的打包流程。 ...

2025年11月12日

二零二三年四月第二周技术周报

在全国范围(如广州、深圳、上海、北京)部署大量客户端的配置下发服务中,面对节假日、活动上线等高并发场景,传统的单地数据中心架构难以支撑。本文分享我当属在“配置下发服务”层面进行的多地部署、数据层优化及缓存机制升级,阐述其技术背景、方案设计、落地实践与收获。 背景 我服务的客户端分布在广州、深圳、上海、北京等全国多个城市,通常需要根据运营策略获取各种动态或静态配置,比如节日活动、功能开关、故障临时屏蔽等。这些配置由服务端统一下发,客户端根据自身属性(地域、版本等)拉取对应内容。 随着客户端规模扩大,特别是在活动上线、客户端批量启动的场景中,配置拉取请求会在短时间内剧增,导致服务端QPS暴涨,严重时甚至会拖垮主服务节点。我虽在容器层实现了全国多地的 Kubernetes Workload 部署,但数据层(包括数据库和 Redis)仍集中在广州。所有请求即便从北京或成都的容器发出,最终还是要跨地域访问广州的数据中心,延迟高、压力集中、体验差。 此外,虽然服务中使用了容器内缓存机制,但考虑到容器本身应保持无状态,其运行内存不适合长时间、海量地持有配置缓存。一旦配置数据规模扩大至几百兆甚至上 GB 级别,运维和资源成本都急剧上升。 为了解决这些问题,我决定从数据层和缓存层入手,进行更彻底的多地部署改造。 设计思路 基于上述挑战,我设计的方案主要包括: 主数据库实例(写 + 读)部署在广州,运营人员在这个实例上进行配置管理、写入操作。 各地部署 只读副本(Read‑Only)数据库实例,例如上海、北京、成都等地。各地容器连接对应地区的只读实例以拉取配置。 Redis 亦做多地部署:每个地域有自己的 Redis 实例,容器拉取缓存优先从本地 Redis;若缓存未命中,再访问本地只读数据库或主数据库。 在配置下发平台中,对不同 Kubernetes Workload 打标签(如地域、版本、标签等),客户端根据标签拉取对应配置。运营人员仍在统一平台管理配置。配置平台写入主数据库,然后同步至只读副本,各地容器读取对应地域的副本。 架构优势 读请求分散到各地只读副本,减轻主数据库压力。 客户端拉配置网络路径变短、延迟下降、体验提升。 配置写入集中在主实例,确保配置管理统一、操作简便。 架构符合“写少、读多”的典型场景:操作多为配置读取,写入(新增活动、屏蔽功能等)相对少。 实施 根据上面的设计,我继续保留广州作为主写中心,同时在全国多个区域(如北京、上海、成都)部署只读副本,配合就近的 Redis 实例,实现配置服务的分布式读请求调度。主数据库仍由运营人员维护和写入更新,通过异步同步机制,将数据分发到各地只读副本。各地的容器服务则连接本地副本,从而避免跨地域拉取配置数据。 Redis 也采用相似策略,在每个部署地域都设立实例,服务端优先从 Redis 缓存中获取所需配置,未命中时才回源数据库。这样既减轻数据库压力,也提升了配置拉取效率。为了让客户端能快速命中就近服务节点,我基于 DNS 做了地域级别的接入调度,同时结合 Kubernetes Workload 的标签机制,自动下发对应配置。所有运营配置仍在部署在广州的“统一平台”进行集中管理,通过主数据库推送同步,容器按标签识别自身身份,拉取所需数据。 落地效果 经过部署实践,这套方案在实际运行中带来了显著改善。各地客户端的配置拉取延迟明显降低,主数据库的读请求压力几乎被完全转移到了只读副本上。整个系统在活动流量高峰时表现稳定,再无集中节点被打爆的风险。 几点经验 虽然配置更新不频繁,但读请求量大;因此采用“主写/多地只读”模型非常匹配。 多地部署数据库及缓存时,必须评估网络延迟、数据同步延迟与一致性需求:对配置下发场景,最终一致性即可,无需强同步。 缓存层(Redis)地域就近部署效果更好,尽可能避免网络访问延迟拖累。 K8s Workload 与标签管理是核心:标签化管理便于灵活控制不同客户端区域/版本对应的配置。 地域入口(DNS 或其他流量导向机制)必须支持解析到就近入口,以保证客户端走最近节点。 容器无状态设计仍然要坚持:缓存尽量外部化,避免容器失效导致缓存丢失或资源浪费。 写 + 同步机制需要监控:虽然配置更新少,但副本同步延迟还是要监控,避免某地配置未及时生效。 安全权限划分要明确:主实例写权限、只读副本只读账户、运营平台权限划分。

2025年11月10日

先行动而非先推理

很多时候,我在做事前都会去考虑很久利弊得失。就比如:我去做了这件事会遇到哪些情况?这些情况下我该怎么办?那些情况下会不如人意?我用的这些个方法真的好吗?等等。不得不说,这伴随了我的很多的决策,特别是在一些比较大的决策下,这些事先的思考真的有用处。所以,我在做很多事情之前,都会去想一下这些问题。如果我的结论是:这件事情目前条件不成熟,或者这件事情很麻烦,亦或者这件事情目前前置条件没达到。那么我就不会去做这件事情,以免投入了很多,却收效甚微。但是,最近我就发现,我这样将这个方法扩大化并不好。因为很多事情,对我来说是新的,在我现有的经历和经验下,对这些事情的认知还处于比较幼稚的阶段。这种情况下,我无法正确地去推理上面说的这些问题,从而去得出正确的结论。 具体来说,利用常识是真的能枚举一些可以预见的情况的,理论上,这些情况会发生。但是,实际上,一件事情,或者一个项目,这些理论上会发生的事情都有它们会发生的概率。这些概率又由许许多多的前置变量决定,如果对这件事情的认知不足,很难说对这些变量抓大放小,找出主要。这样就容易放大一些变量的影响,最终导致预测的事情其实并不会发生。另外,在很多情况下,我也会对一件事物的性质和反应对出某种推断,特别是对一类人的推断,比如说我觉得某些人不友善,某些人友善这样。但实际上,在你真正地接触这些人后,你才能知道他们是否真如你想得那样友善。或者说,他们对于你和他们接触的真正反应,只有你去接触后才知道。这些事情是无法进行预先推断的,除非建立在你经常和别人打交道的基础上。所以,在很多时候,我对于某些事物,某些人群的判断,是先入为主的,是从别人那里听来的,模糊的,加入了自己想象的一种判断。大多数时候,这些判断并不准确。如果我想让这些判断准确,那么我就要先行动起来,去做这些事情。 先行动,也就是说在对事情有个轮廓,模糊的前提下,优先行动起来,放低姿态去尝试,然后再根据反馈的结果看看是否可行。如果反馈良好,完全可以继续行动。如果反馈不好,也不必立即收手,可以分析一下原因,或者拿着这些具体的案例问问其他过来人。不论哪一种情况,都比先不动,然后想一大堆,这样如何那样如何,来得具体、实在与真切。很多我们认为实践中会遇到的比较大的问题,往往会在真正的实践中找到思路。反之,在实践中真正遇到的大的问题,在想法中往往预料不到。所以,先激活自己,去迈出第一步,在对事情认知比较模糊的情况下,比什么都重要。 先推理,我认为是建立在对事情已经有一个比较成熟的把握的情况下,特别是在这个事情所述的领域,我并不是一个纯粹的新手。而推理的准确度,建立在我在这个领域的学习与实践的成果的叠加。也就是说,推理需要真真切切的样本,这些样本需要自己得到,而不是纯粹从别人那里“听来”,更重要地是自己通过实践“寻来”。这种情况下,有了这些样本作为基础,对事情的推理预测的把握才能更大,对于事物的认知才能更加准确。这样,后续就能不断靠推理来规避一些在这该领域下的陷阱,从而让自己能够跑得更快更稳。 如果综合上面的论述,我可以发现,总是应该先去行动,因为推理没有行动的结果作为支撑,是摇摇欲坠的。先行动而非先推理,确实是可以作为我在对于一些自己并不熟悉的领域的一种行事的习惯,从而避免先入为主,然后想太多,最后什么都没做,白白错过了可能的机遇。

2025年11月10日

让树莓派 5 跑上“香草”主线内核:精简裁剪、交叉编译与部署全流程

摘要 本文系统记录了我如何将树莓派 5 打造成主线 Linux 内核(Vanilla Kernel)的极简开发与测试平台——包括仓库极简裁剪思路、定制化内核配置、交叉编译与自动部署脚本、overlay 移除技巧,以及长期与主线社区同步维护的实战心得。适合关注 Linux 主线、ARM64 适配、内核开发、Upstream 贡献或喜欢折腾内核的极客们参考和实践。 背景与动机 最近一段时间,我在深入研究 Linux Kernel 的理论和架构,准备正式切入操作系统内核领域。因此,特意购买了树莓派 5 作为开发板,用于学习和实践内核开发。然而刚上手就发现,目前树莓派 5 并不能直接跑主线 Linux 内核,只能依赖树莓派团队维护的内核仓库。当时最新的分支是 rpi-6.14.y(现在是 rpi-6.16.y),仓库的维护方式是“动态拉取上游主线代码+rebase+大量自定义补丁”,涉及上万行的修改和历史反复变动。 这些定制补丁对于官方内核在树莓派上稳定运行、兼容所有功能确实非常重要,但对我这种纯粹想研究主线内核特性、关注内核本身的开发者来说,这些补丁反而变成了负担: 主线同步困难,merge 经常出冲突, 版本历史频繁重写,难以回溯和比较, 验证主线新特性或做反馈变得不直观。 我对比了 x86 开发板,发现它们大多能直接运行主线内核,但价格昂贵,性价比远不如树莓派 5。因此,我开始思考,如何让树莓派 5 也能顺畅运行主线内核,真正实现随时体验和测试内核最新特性,并为 ARM64 平台反馈问题、贡献补丁提供便利。我的目标很明确:用最少的补丁、最贴近 upstream 的方式,让树莓派 5 成为个人主线内核实验和学习的理想 ARM64 平台。 仓库结构与修改范围 我的做法十分简单却又直奔核心——只保留最必要的驱动与补丁,其余全部紧跟主线。仅对 drivers/ 目录做补丁,聚焦核心硬件支持,主要包括: SD 卡驱动:保证树莓派能正常引导,内核能加载根文件系统。虽然启动固件自带部分初始化功能,但主线内核仍需驱动挂载根文件系统。 有线网卡(Ethernet):方便通过 SSH 实现远程登录、下载新内核镜像、远程开发和调试,是内核开发过程中最重要的 IO 通道之一。 UART 串口:作为内核调试和 early print 的输出窗口,可以直接观察内核日志,判断启动和运行状态。 USB 控制器:用于外部设备挂载或偶尔传输离线数据,兼容更多实验场景。 基本 GPIO、电源管理等与 RPi5 硬件紧密相关的部分。 剔除官方内核中与 Wi-Fi、蓝牙、GPU(VideoCore)、HDMI、音视频加速等非内核开发必需的子系统支持,这些对内核本身的研究和测试意义不大。 维护 arch/arm64/boot/dts/ 下的树莓派 5 DTS(Device Tree)配置 ...

2025年6月14日

搭建个人链式VPN网络:高效隐私保护、设备无限制管理与远程访问

在某些场景中,我们可能需要搭建一个链式 VPN 结构,即先让内网设备连接到自建的 WireGuard 服务器(如个人 VPS),再通过该服务器将所有流量转发到另一个 VPN(公司、学校或商业 VPN 服务)后才进入互联网。搭建这种「VPN 套 VPN」的网络架构,有以下明显优势: 强大的隐私保护:内网设备的数据流量首先通过自己搭建的 WireGuard 加密隧道传输至个人 VPS,再由 VPS 二次通过商业或公司 VPN 进行转发,最终从该 VPN 的 IP 出口访问互联网。这种方式彻底避免了使用 VPS 自身 IP 地址直接访问目标网络,大大降低了个人 IP 被跟踪或泄露的风险。 集中化便捷管理:所有客户端设备统一连接到单个 WireGuard 服务,免去逐一配置每台设备的麻烦。更重要的是,它还能巧妙绕过一些商业 VPN 服务商设定的账号设备数量限制,一次付费,所有设备共享。 灵活便捷,轻松切换:由于商业或公司 VPN 配置仅在自建 WireGuard 服务器端修改一次即可生效,下游客户端(手机、电脑等)无需单独调整。这使得更换或重新配置上游 VPN 时更加迅速和便捷,大大提高日常维护的效率。 打造专属虚拟局域网,实现跨设备互通:一旦所有设备连接到自建的 WireGuard VPN 服务器后,将自动获得统一的 VPN 虚拟局域网 IP 地址(例如:10.8.0.0/16)。处于该网络中的设备能够直接互相通信,无需额外设置。 这种便捷性意味着无论你身处何处,都能轻松实现: 远程 SSH 登录:随时访问家中或办公室的服务器。 私有 NAS 媒体访问:在外轻松访问家中 NAS 存储的媒体文件。 远程办公:随时随地安全访问公司内部网络资源。 方案 经过本人的探索和实践,我逐步摸索出了自己的一套链式 VPN 方案。总体来看,我的方案完全基于 Docker 部署,所用到的核心组件有: gluetun:一个 Docker 镜像,内置 OpenVPN/ WireGuard 客户端,可连接到各大 VPN 服务提供商。本例中我们通过配置在 Docker 网络中提供 VPN 网关功能。 wg-easy:一个简化的 WireGuard Docker 镜像,内置 Web 界面,便于创建/管理 WireGuard 配置文件。下游设备通过 WireGuard 客户端连接到该服务,所有流量将被转发到 gluetun 容器,由 gluetun 再发给上游 VPN。 该方案的优势在于完全容器化部署,避免污染服务器/VPS 的环境。然后就是比较稳定,个人日常使用未发现明显延迟、卡顿、断流问题,而且性能良好(在个人家用带宽下测试)。另外,该方案现在能够支持 IPv6,即使设备身处 IPv4 的网络环境,也能通过链路实现对 IPv6 资源的代理访问。 ...

2025年6月2日

关于对事物定性判断的一些体会

在现在,生活中充斥着各种信息。我能够从各种消息来源获取各类信息,比如新闻、社交平台、长短视频等等。在这些信息中,包含着对周遭世界的各种变化的概要情况,或者是细节分析。其中不妨也有一些基于现在的情况,对一件事情进行定论和判断。有的分析往左边说,有的分析往右边说,有的分析自称“中立”。总之,众说纷纭,莫衷一是。我也是很好奇,我该如何判断这件事情的性质?哪一些是“对”的,哪一些是“错”的? 很多时候,我发现人们对一件事情的判断,其实是处处受到其自身所处的状态的影响。自身所处的状态又受到其社会地位、人际关系、经济实力、自身经历等影响。而且,对于一件事情,若非当事人,旁观者从各种消息中得到的总是这个事件的某个描述,描述者也将会有意无意在描述中渗入一些倾向性,这是不可避免的。比如说,虽然这个描述者说得都是事实,但是对于事实的描述也可以有侧重。有些他觉得重要的或者有意义的细节,他会多说。而另一些他认为不重要的,当然就会少说,甚至直接忽略。一件事情,除了其发生的时候的过程以外,还涉及到各种背景,包括但不限于参与者各自的背景,还有事情发生之前的各种关联性事件,甚至当事人在某一时刻闪过的想法。如果要在描述中,把这些事情都交代详尽,那是不可能的,更不用说有些细节根本难以探知。 很多时候我注意到,对于一件事情、一个事物或者一种现象,从两个完全不同的立场出发,完全可以各自发展出一套非常有道理且充满正义的说法。然而从这两个不同的立场看对方,往往会轻易给出非常尖锐的批评,双方都可以援引各种实际的例子。有趣的是,这些批评可以互相套用,用在各自身上,从切换一下立场,然后按照这个立场的模式进行一套有逻辑的推理,即可很好对号入座。所以,我尝试对我身边发生的事情、我感兴趣的大事、我忧虑的事情和我日常的小事等等进行这样的正反分析,几乎都能得出两个相反的判断和结论。在这段时间内,我陷入了一种分裂。我经常苦苦思索,到底哪一种说法是对的,按照事情的发展,哪一种立场可以笑到最后。我也渴望,一种中立、理性、客观的立场。然而,每当我认为某个人阐述的这种立场非常客观,非常中立,一段时间后就会有针对持有这种立场的人的批评之声。要么就是,客观到味同嚼蜡,我都不想继续继续听下去。我为什么会这样,我常常反思。 在长达几年的断断续续地思考后,我最终得出我自己的结论,寻找一种”中立“且”客观“的立场观点,或者是对一件事物去苦苦追寻”真相“都毫无意义。这样反而会让自己活得非常累。根本的原因在于,我自己是有自己的立场的,不需要再去强迫自己接受其他人的立场。我的立场来源于个人喜好、社会地位、经济实力、过往经历、经验见识等等,这些也是我上面所提到的影响个人对于事物判断的几个影响点。所以,我会在某些时候,对于一些声音感觉很”客观“,对于另一些声音觉得很反感。那是因为,其实对于这些事情早已经在自己的内心产生的自己的判断。那为什么我不能去乐呵呵地接受所有的声音?我承认,我可以去听取所有的声音,并思考其他的声音中包含的那些道理,然后尝试去加深自己对于一件事情的认识,但其实很难做到去开心地接受。然后,让我去站在我自己的对立面去思考,完全可以,但是对于我自己并无意义,因为我毕竟无法成为对立面的那一些人,很难按照他们的立场来做或者享受这样的立场带来的利益。 我更加倾向于认为,每个人都有自己的基本法,基本法是按照自己的个人情况量身定制,是自己对于周遭世界该怎么样才会“完美”的朴素认识。我自己是立法者,为了对于一件事情进行认识和判断,我可以通过基本法衍生出一些实际的法律。我自己也是法官,根据基本法衍生出的现行法律,对一件事情进行定性和判断。我是否要去参考其他人的定性和判断,可以,但是不是必须,应该更多地秉持着一种认识世界上的思想多样性的态度。别人如何判断,那是基于其他人的基本法,他们的基本法可能与我有相似之处,也可能与我大相径庭,完全相同的可能性非常小。那既然不是同一套基本法,那么各自也会衍生出自己的一套实际的法律。既然他们与我所处的体系都不同,我为什么要完全接受一种立场? 对待日常生活中,那些自称的”中立者“的态度,基于我自己的这套观点,也有了一些清晰的方法应对。这些中立者,其中一类,无非是宣称自己所述的是事情的真相,但是前面已经说过,一件事情的真相是很难原汁原味的还原的,各类描述都带有自己的侧重,而且总有不为人知的细节未曾曝光。既然这样,那空谈真相还有何意义,即使事情有一个绝对的争相,那理论上也只有神知道了。所以我觉得,我大可放下“追求真相”的精神洁癖,基于我自己所知道的,去给出我自己的看法。还有一类,说自己的观点很“中立”和“客观”,这是可笑的,因为他也是一个人,有自己的基本法,如何能抛却七情六欲,成为一个神仙,从而来达到“中立”和“客观”。如果有,那就有一种相对的中立和客观,那就是穷尽世界上的所有的立场与其对应的思想观点,以及他们对于这件事情的看法,然后全部精确记录并阐述。有些“中立者”正在向着这方面努力,这类人,我觉得确实是往正确的方向在走。但是,我作为一个人,听取这么多的立场有什么意义,难道我能随心所遇地切换自己的人生,来去实地体会和践行这些思想观点和判断?类似地,就比如一个问题有即使多种解决的方法,但是最终我还是会用一种方法去解决它,选择哪一种方法完全是个人的事情。所以在这里,我认为某个事情该怎么判断,听从自己基于自己立场的判断即可,在自己的这个参考系下,这就是中立和客观的了。其他的参考系下的看法,又与自己何干?当然,我们也可以多看看其他的参考系,但这绝不能作为一个强制的要求。如果自己想真正独立,那么维护自己的参考系的独立性,捍卫自己的是立场、观点和偏好,是非常重要的。 我也注意到,对于基本法某些部分或者条文有比较多相似性的一些人,是完全可能聚合成一个团体的。一个人独自战斗地久了,找到和自己有相似观点的人,还是一件很欣喜的事情的。对于一些事情,一个人完全可以去发现和寻找自己的团体,不一定要加入和做些什么实际的事情,也可以是一种内心的靠拢。很多时候,人们会为了一件事情的看法,相互攻讦,给不同立场的人扣上一顶帽子。如果这个时候,一个人能够站在一个自己舒服的团体下,那可以减轻很多压力并有可能产生一种安定感。很多情况下,我觉得“站队”是对个人有利的。更多的人如果能通过“站队”,共同努力去维护和传播一种相似的立场,才能让一种“共同立场”持续存在并产生力量去真正推动现实中的一些事情。这样才有可能,在大家去解决某个社会性问题时去考虑这种立场,然后去采取这种立场提供的方法或者部分思路。如果真能这样,个人才能够在这种情况下获得更多的利益,让自己觉得自己的观点偏好与自己所喜爱的有了更多的意义。 很多人说,自己“看开了”,没有太多的想法了或者是总想看看中立的或者什么都不看,我觉得不如说自己“绝望了”或者“放弃了”。既然上面说,出于自己的特性和基本法,个人总有自己的立场,这种情况下,如果“看开了”,想让自己陷入一种“中立”,那其实是抛弃了自己的基本法,把原来属于自己的权利——对一件事情保留自己的观点和看法,让渡给了他人,然后自己甘愿随波逐流。甚至有“看开”的人说,那些没有看开的人,“幼稚”且“不成熟”,他们所汲汲或戚戚于的事情,很多是陷入世俗的不成熟的表现。然而,在所谓“幼稚”的人看来,这些“看开”者无非也就是一个自我精神阉割的可悲个体,由于世俗的压力丧失了属于自己的基本权利,甘愿做一个随波逐流的空壳。看似“廓然无累”,其实任由他人摆布,这种摆布不限于观点立场的通过潜移默化的强制接受,而且也会通过其他观点掌握绝对话语权后,通过提出属于他们解决社会问题的方案来产生个人利益的受损。所以,我们也无须去贩卖“成熟”,或者是感到沾沾自喜。基于世界上思想的多样性,成熟也是一个相对的概念了,社会主流立场所看来的“成熟”,在另一些立场看来,反而是幼稚且不成熟的。关键就在于,那些立场掌握了这个社会的话语权。 想了这么久,终于能够把自己一部分对于这个主题的思考比较具体地阐述出来,这也是一件好事,能够认识到自己的某一条法律条文究竟该怎么写。当然,基于我自己所想的,我自己所写下的也是一家之言,也收到自己所叙述的东西约束。就是说上面这些,完全可以归为一种属于我相对的观点,从读者的立场看,这些可以没有任何作用,完全可以不以为然,甚至可以完全荒谬或者全盘错误。但这种观点或者理论,对我有效,让我对周遭世界有一个我自己认为自洽的阐释,那就够了。而我写出来,对于他人的意义在哪里?用我自己的话来说,就是能够让别人在愿意的时候,去体会一下这个世界上的思想多样性。

2025年3月6日

二零二三年四月第一周技术周报

看了一下当时的周报,值得说的事情也就是关于日志整理的事情,然后就是关于一个配置下发服务的多地部署。关于多地部署的事情,下一篇技术周报再详细说明。 首先来说说,日志方面的事情。现在的话,线上有很多服务。然后目前 DEBUG 的日志,是用了腾讯云的 CLS。而目前存在一些问题,这些服务的日志有些分散,不太好找。如果一个请求跨越了多个服务,有可能需要研发去跨越几个日志集然后来定位问题。然后就是,这些服务技术栈并不统一,并没有一个统一的日志监控框架来监控这些服务的状态。目前追踪跨服务的请求使用 TraceID,TraceID 是在请求进入网关服务或者入口服务的时候自动生成的。TraceID 有可能是纯数字,也有可能是数字带字母。后续的服务之间的请求基本上会带有 TraceID 这个字段,用于追踪请求。然而,也还有很多请求内容里不带 TraceID,导致服务没办法从请求本身来拿到 TraceID,只能索性重新生成一个,所以这些请求在各个服务之间的 TraceID 都会不同。总而言之,理论上,正常情况下,一个 TraceID 将伴随一个请求,从进入追踪范围到出追踪范围为止。 另外还有个问题,就是这些个日志集中,日志内带有的字段也不太统一,有写日志集的请求内容的字段叫 X,有些又叫 Y。有些字段日志的结构上有,实际输出的日志中却没有填写。如此这些,都增加了新的研发上手定位 BUG 原因的学习成本。上述这些问题,都很让人头疼。 还有一个就是降本增效的问题,在目前的已有日志的基础上需要看看还有哪些日志可以精简优化的。整个部门,每个月的单单存储和整理日志成本都十多万。而我负责的这一块的业务量又占大头,日志量也是非常大的,每天大概有 540 亿条日志。所以,我需要在这一块多下功夫。 对于当前的的 TraceID 问题,我采取先把同样技术栈服务的 TraceID 的生成方式和传递方式都改为统一的。对于 Java 技术栈的服务,用的无非都是 Spring Boot 框架。所以我对每个服务都增加了一个统一的拦截器,来拦截请求和响应。对于请求,这些 Java 服务处理的都是文本类型的请求,基本上都是 JSON 格式编码的。所以,无外乎就是检查 JSON 字段中是否有相应的 traceId 字段,如果有的话,那就拿来用。具体就是把 TraceID 放置到线程私有化存储中(MDC),伴随着这个线程处理。这个方法也有局限性,有些请求会在某些步骤上进行异步处理,转移到其他线程池的线程上处理了。这样,TraceID 的追踪就丢失了。我的办法是写一个包装类,接管所有异步处理的需求,参数与调用方式和原来一模一样,然后在其中检查上一个线程中是否含有 TraceID 字段,然后把 TraceID 字段先放到包装类产生的对象中。切换到另一个线程后,这个线程先看看包装类对象中是否有上一个线程存下的 TraceID,如果有就提取这个 TraceID 然后记录到自己的私有化存储中。这样 TraceID 的追踪就不会中断了。 请求拦截器的 TraceID 生成过程,也可以提一下。由于很多以前的老服务使用了二进制的请求格式,技术原因导致了这些请求无法灵活变更。一旦更改了请求,那么上下游都需要重新编译生成编解码器,然后重新部署。这对于日志改造来说,工作量不可接受。然后有很多请求中,TraceID 是 long 类型的,而非字符串。所以我就需要照顾这些老服务,大家都统一使用 long 类型的 TraceID。然后,我还希望在 TraceID 中带有一些额外的信息,比如说这个请求是从哪个地方被赋予 TraceID 的。如果这个信息能直接从 TraceID 中看出来,那就再好不过了。所以我不是简单地随机生成一串数字当作 TraceID,而是保留前面的 4 位数字。前两位用 99 作为开头,表示这个是使用了统一后的 TraceID 方案,和以前的纯数字的 TraceID 区分开来。然后,后两位数字按序号表示各个服务。01,表示网关服务 A;02,表示入口服务 B 等等,这个我来手动分配,最后会把分配方案写到文档中。后面的数字,都是随机生成的。 ...

2024年11月17日

二零二三年三月第四周技术周报

本周有个插入的事项,就是说我们目前在使用某云服务商的 API,对于其中的 API 鉴权,那边有更安全的方案。原先,我们应用调取云产品的功能时,是通过一个 AccessKey 和 AccessSecret 来的。在调用的时候,需要在 SDK 中提供这两个值,然后就可以在应用中使用 SDK 提供的云产品 API 了。原先的安全要求是不能在代码中存储这两个值,公司会有某种自动扫描机制,通过这个机制可以检测代码仓库中的敏感信息,这两个鉴权用的字段也当然囊括在内。所以,我们一般都会把这两个值写在配置文件中,安全性会更好一些,也方便更改。 而现在他们说这两个值是静态的,无法在泄露的情况下快速进行更换。毕竟我们服务一般都有几十上百个容器,而目前来说,启动配置虽然已经集中于各种配置中心里了,但是在技术上是无法实现秒级别的动态更改的,必须在修改配置后重新启动容器。而容器的启动是无法通过一次性重启所有容器来完成,必须进行分批重启以保证服务的平稳,没有十几分钟甚至几十分钟是搞不定的。 所以云提供商的技术团队提出了一个新的方案,通过向我们提供一些必要的而且泄露后不容易造成直接影响的凭据(好像是一个密钥文件),然后通过在线认证的机制下发动态的验证信息。这个过程比我上文提到的验证机制复杂许多,特别是我们需要在容器中纳入这样一个配置文件,或者直接在 JAR 包中纳入。然后我们需要引入一个 SDK,来专门做这个动态验证信息的获取,然后将这个信息通过某种方式传递给云产品的 SDK,最后才能实现云产品 API 的调用。 这个具体的过程并不是我来跟进,我把这些问题交给其他同事了,由他们来具体执行。我只是作为云账号权限的控制者,为他们提供密钥文件并给他们提供正确的方向即可。

2024年4月15日

二零二三年三月第三周技术周报

本周值得注意的事项就是,测试人员那边反馈在测试环境下,某些账号突然无法正常使用的问题。目前整个账号的体系还是在沿用老的系统。而在当前老系统中,账号信息使用某种特殊的数据存储分成多个模块存储,每个模块相当于一张表。每张表都各有侧重,有些侧重于与其他账号之间的关联关系,有些侧重于查询等等。这些账号无法使用的问题在于,其中最重要的一张表也就是主表,这个用户的记录消失了。在其他表中该用户的相关记录还是存在的。 这就引发了我的各种猜想,是否是测试人员测试的过程中的环境弄混了?或者是某个服务的配置有问题导致了部分应该写在测试环境的数据写到了生产环境去了?说代码写得有问题导致了某些极端情况下,用户的主表信息写入失败?通过调查发现,最终要的查询表中,数据结构还是完整的,而且能够反推出用户注册时候的微信账号。查询表只有在账号创建的时候才会去写,后续基本不会再修改了。而主表中的数据应该是和查询表在用户注册的时候一块写入。 对于这个问题,后续推断应该是数据模块发生了某种问题导致数据丢失。而在我咨询数据模块相关维护人员的时候,我还没说清楚问题,他们就说是测试环境并不保证服务的可用性,可能发生任何问题。那么这个问题可能就无解了,因为如果是数据模块这层问题,我作为使用方基本没有任何能力来自己解决。除非我能替换掉这个数据模块,使用更加稳定可靠的解决方案。那对于这个问题,我只能够然同事手动清理查询表中的索引关系,然后在让用户重新进入应用。当用户重新进入应用的时候,由于没有索引就相当于没有这个用户,此时就会重新创建用户。后续几个月遇到了十几例相同的问题,没有办法,让同事添加了一段自动处理这个问题的业务代码,以免去每次手动处理的麻烦。 这个问题第一次出现将近一年后,由于某个重要客户反馈了这个问题。而又正好这个重要客户当时半个月之内反馈的我们的各种问题有点多,领导对此十分困扰,所以对于我这个问题他特别重视。领导后续找到我让我推动解决这件事,我同意执行,因为我也想知道问题具体是什么。现在能够确定的是,问题出现在数据存储层,而这一层又不是我们直接控制,我们只是使用这样一个产品。这个产品已经很老了,维护人员很少了,而且还有其他事情。他们不愿意来主动寻找问题,也是情有可原的。所以想要推动他们的维护人员来解决,就只能抓住具体且直接的证据来证明他们的问题在哪里。 我以当时的了解,知道数据层分为缓存和落盘数据库,而且经过一年在各种问题的解决过程中学习,我也能很熟练调取分析落盘数据库里的数据了。我调取了落盘数据库中的用户 ID 的倒序情况,发现从某个用户 ID 开始,后面就没有了。最近一年,用户报问题的用户 ID 以及新注册的用户 ID 是远远大于这个 ID 的。我又去日志中获取了一个最新注册的用户 ID,查询数据层,发现是有的。说明缓存中是有正常数据的,但是由于某种原因数据并没有落到数据库中,而是在缓存满后直接消失了。 这已经能够说明问题了。于是我将这些调查记录截图发给数据层产品的维护人员,并提了一个工单,推动他们来解决这个问题。该问题的脉络我已经提前分析地很清楚明了了,而且又有直接的证据,他们也开始认真对待这个问题。后面确实,他们重启某个模块后,这个问题就好了。而且最终他们承认问题与缓存层落库的机制有关系。

2024年4月15日