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

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

2024年4月15日

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

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

2024年4月15日

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

本周相关的一些事项,我认为最值得讲得就是某个数据库接入层服务的给每一条数据加上键 过期时间。这个数据库接入层是一个 Java 服务,本质上做的工作就是将 Redis 与数据库 结合起来。对上的接口遵循某一个公司内部的特定协议,而对下则使用开源且很多人使用的 Java 包来访问 Redis 或者数据库。访问 Redis 使用的是 lettuce,而访问数据库用的就 是 mybatis。原来访问 Redis 其实用的是 Jedis,后面之所以改用 lettuce,是因为它在 高并发下的性能/资源表现更好一些。我认为具体地主要体现在多路复用连接,使得不需要 在连接池中维护很多连接,总之就是在连接上的开销不会很大。 然后对于数据库来说,并非直接写入,而是在消息队列中写入一条插入指令并附上一些上下 文。这样消息中包含了写入数据库需要的所有信息了。然后这条消息队列也是自己在消费, 消费的时候将数据写入数据库,如果成功就 ACK 消息,失败了就让这条消息 5 分钟后再回 来。 这个服务总的读写策略就是,如果一条数据查询来了,Redis 中没有数据时就将数据库中的 数据拉到 Redis。然后返回 Redis 中的数据。Redis 中有数据则直接返回。对于写入数据 来说,这条数据会双写 Redis 和数据库。修改数据的时候,操作复杂得多。因为这个 Redis 中数据是使用 HSET 的方式进行存储的,然后一条修改指令可能只是修改 HSET 中的 一部分字段。这个就需要对一些具体的情况进行讨论了,数据库指令也是根据具体情况来构 造。 值得一提的事写数据库的时候,目前看前人升级了策略,不直接拿消息中的数据来写,而是 消费到消息时,直接从 Redis 中读数据(这个时候 Redis 是早就被双写了的),然后直接 将 Redis 中读区到的数据写入数据库。我认为这样做的好处是,让数据库中的信息尽快更 新。因为从消息投递到消息队列再到消费的过程中,这一条数据可能已经修改了好几次了。 在这几周我发现这个服务在 Redis 中的缓存并未设置过期时间,而是依赖 Redis 实例中设 置的统一过期策略来进行数据淘汰。这个策略是在 Redis 数据存满的时候才会执行的,这 就会引入一个风险。根据我们云上分布式 Redis 的设计,如果 Redis 存储一直都是满的然 后这个时候有大量的数据写入操作(某种特殊的业务高峰期),这样云上分布式 Redis 为 了一次性腾出足够的空间会集中进行淘汰操作。这个时候无法处理任何请求,这种情况将持 续几分钟。这对于业务来说,存在较大影响,因为这个时候上游业务服务将无法读或者写入 数据了。而现实情况就是,这个服务的 Redis 一直处于满的状态。 ...

2024年4月14日

关于个人对AWS服务的使用感受

原文经过 AI 润色,非本人文风。 从 2022 年 11 月开始,我开始使用 AWS 服务,已经接近 1 年的时间了,而且不是免费套 餐。从一开始对它的使用感到新奇和陌生,到现在对它的了解更加深入,期间经历了很多有 趣的事情。那么,为什么我选择使用 AWS 呢?因为我个人有一些需要在 AWS 上运行的服 务,比如博客网站(Wordpress)、Git 仓库、RSS 订阅等等。当时,我觉得 AWS 在云服务 市场占有最大的份额,所以它的解决方案应该已经非常成熟了。对于 AWS,我还是有一定的 信任,愿意花钱购买适合的服务,以确保我的个人托管业务能够长期稳定运行。 在开始使用 AWS 之前,我也尝试过免费套餐,但是一直没有找到合适的需求场景,所以用 了一段时间后就不再使用了。这样,我的免费套餐周期就白白浪费了。 当我第一次接触 AWS 的时候,我首先尝试了 EC2 产品,并试用了 1 核心 1G 内存的 T2.micro 实例,但后来发现内存不够用,导致机器卡死。因此,我决定换成 T2.small 型 机器,它拥有 1 核心 2G 内存,并且我还增加了 SWAP 分区,每个月大概需要花费 17 美 元。虽然这个价格包括了带宽费用,但我仍然觉得有些贵。然后,我开始考虑是否可以使用 AWS 的保留实例,听说可以减少 60%-70%的费用,看起来非常划算。于是,我下定决心购买 了三年的保留实例,先支付了 170 美元,然后每个月承诺支付 4.75 美元。这样一来,每 个月的费用大约是 8.84 美元,这个价格看起来相对合理。 然后遇到了一个坑,当时我配置了 30G 的系统盘和 10G 的数据盘,以为 30G 的系统盘是 免费的。然而,我又考虑到数据的安全性,因此还增加了定期快照备份,以为快照的费用不 会很多。最后,月度账单一出来,竟然要 20 美元!我很惊讶钱都花在哪里了? ...

2023年11月24日

关于主流道路的一些思考

一般而言,我们总是倾向于捡着他人走过的路来走。一旦偏离这个所谓的“大众路线”,我们 很可能就会觉得自己处于悬空状态,从而陷入一种循环地自我怀疑中,总认为自己处于一种 脚不着地的危险的状态。我感觉这是我们倾向于认为,大众的某种“主流”的观念是一种无可 辩驳地正确,从而不会去思考其中正确性来源的几方观念的出发点,以及这些观念背后的逻 辑。 具体来说在我们的生活中,常常会有一种压力,让我们按照主流的方式去行动。这种压力直 接来自社会与文化的期望、他人的看法以及我们自身对所谓“成功”的刻板印象。我们害怕偏 离主流,也害怕在便利主流后失败,从而被周围的人认为是典型异类或失败者的活生生的例 子。因此,我们倾向于随着周围的人选择安全、确定的道路,即那些大家已经验证过的方 法。顺从主流方式可以当然可以带来一些实实在在的成果,但这种方式并不一定最适合作为 单独个体的某个人,你可能并未从自身的具体情况的细节出发来选择,而可能带有一定的盲 目性。我们不断仰视其他人的成功,给自己制造一个个“神像”,时时刻刻去回忆去参拜,在 谈论中夹杂这感叹、羡慕或者是丝丝的嫉妒。但我们强调要像这些“神像”看齐,要成为他 们,但却始终忽视了自己的特点。我能不能成为另一种定义下的“神像”?这个是很多人都很 少去思考的问题,很多时候大多是人可能认为这种思考无意义。 这个时候想象一下,如果别人向你寻求有关他们当下困难的决定或者说是某段时期的人生道 路,你可能会毫无思考地说出这些“主流”方式,但当别人进一步问你为什么这些方式是正确 的?是否可以列出实际的步骤并从自身出发去论证这些步骤的合理性?你可能第一时间就不 知道如何回答了。 虽然说,当你跳出固有的“大众”思维去思考的时候,想着我可否在这个主干道的这个岔路口 上走拐进另一条路。这可能会产生很多困惑,因为这对于你思维的惯性来说,何尝不是一次 急刹车?也许你第一次考虑一种未曾设想的可能性。并且有可能突然发现原来这件事的逻辑 是非常复杂的并陷入一种思维错乱中。在你尝试你认为是正确的新方法时,也有可能发现你 其实是走了弯路。最后你可能会得出结论,主流方法也是有一定道理的,是基本上正确的。 然而,只有意识到这些,有意识地去了解这些,在拥有可行性的基础上去试着尝试这些,你 才能在不断尝试和试错中更清晰地把握事情的本质,最终正确认识“主流”方式的合理性以及 它为什么会存在。这个时候你的脚或许真正开始走在实地上,这大概会逐渐在内心深处建立 处一种内在的自信,不断引导你尝试其他的可能性,从而有可能找到最适合你个人特质和当 下实际情况的道路。 所以,我个人认为不要因为害怕事情的复杂性而放弃寻找其他解决方法的可能性。这个时候 要相信自己的现有的能力和基于大量有效思考而对事物判断产生的直觉,不要过分依赖他人 的意见和观点,从而自己的一套思想理论和解决问题的宏观的体系。最重要的是,要始终坚 持自己的价值观和目标,顺着这个内心的最终指引找到真正适合道路,才能有效避免在大量 的思考和尝试汇总迷失方向。

2023年11月21日

自建图床服务并配置免费CDN全球加速 过程记录

个人有一些图片托管的需求,在很多场景下比如说在博客文章中插入图片,在社交网站上分 享图片,抑或是个人开源项目中的图片等等,都需要在文档(特别是 MarkDown)中嵌入一 个 URL 直链来指向图片。对于一些免费的图床服务商,个人感觉并不是很信任。数据安全 是一方面,另一方面如果服务商跑路了,那么很多原先我创建的链接都会失效。据我所知, 这样的例子比比皆是。 网上还有一些教程将 GitHub 当成免费图床来用。这样做弊端也很明显,GitHub 在国内的 访问速度非常慢,图片加载很慢。另外,缺少很多图床该有的功能,比如说自动压缩,自动 转换成 Webp,自动重命名等等。这样做也是不太可行的。下面就是一个我将 GitHub 用作 图床的例子。每次我还需要将图片移动到代码仓库里,然后 commit,push,我个人感觉很 麻烦。 还有一段时间我试过把 WordPress 的 Midea 库当作图床来使用,但是我发现图片链接中带 有 wp-content/upload 这样的路径。然后链接中还会出现图片的名称。我感觉并不是很雅 观,而且在隐私的保护中有一种说不好的感觉。就像是别人一看这个 URL 链接,就能获知 一些图片以外的信息。下面就是用 WordPress 的 Media 库当图床例子。可以看到,产生的 直链不是很美观,而且还能暴露一些信息。 所以,我这边产生了自建图床服务的想法。这种图床最好能自动转换 jpg、png 格式的图片 为 webp。这样图片就能比较高质量、快速地在用户浏览器中加载出来。有关 WebP 的简要 介绍如下: WebP 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. ...

2023年11月18日

二零二三年三月第一周周报

最近,合规的要求逐渐渗透到技术层面。最近的一段时间,总有产品来找,说要实现这样那 样的合规需求。或者是,做一些什么合规的调查问卷。我感觉,合规也主要就是说对于用户 信息的存储、访问需要规范化,然后用户能够逐渐开始掌控自己的数据。然后还有一些人事 上、组织架构上的变动对于技术层面造成的冲击,比如说一个业务被调度到其他部门去了, 然后我们还在一直和这个业务共享数据库等资源。这个时候费用问题就突出了,就是说钱该 算在那边。虽然是一个公司、一个事业群,但感觉可能是由于公司内部的核算机制问题,对 于这些成本问题还是比较较真的,总会掰扯什么:他们用了我们的数据库,钱还在我们这边 什么的。有时候感觉,这些都是属于内部消耗,并不是出于发展的眼光看待问题。另外,别 的业务可能由于什么需求调整,需要修改一些公用的数据库结构或者接口,就比较麻烦了。 因为这个时候,我们为他们花费工时来做这些事情,并不能得到任何肯定。但是,又会被那 边的人催着,天天轰炸。自己夹在中间,感觉就比较难受。 然后,我上个周报提到的 PHP 网关迁移的问 题,其实前一两个月就开始在做了。其实也早就写好了,但是在后续切换流量到新服务的时 候,总是会遇到这样那样的问题。具体表现就是,测试环境流量切换很久了没有人来找,一 切正式环境这样那样的人就来找了。所这个接口突然用不了,那个接口突然报错什么的。这 种情况下,由于问题发生在线上,只能先把流量切回去再分析问题。一来二回,一个星期就 过去了。有时候,切换了 4、5 天才会有人来找。这样一个流程下来久更久了。 leader 一直说这件事情做了很久如何,但实际的情况就是这样。虽然这个服务的后续具体 的代码编写和维护工作并非我来做了,但是我也知道一个新的服务要能够完全替代就有的服 务必须有一个这样的流程。因为在缺乏文档和测试用例的情况下,你不知道一个接口有什么 暗藏的机制,也无法完全清楚我写的代码是否和原来的等效。所以执行层面,很多事情没有 明确汇报,但是我感觉管理上也要能体会到。虽然过程比较反复、艰难,但最终这个服务的 重构工作在本周还是完成了,所有的流量都迁移到新的服务上了。也就是代表,这个老 PHP 网关的生命周期终结了。 我接手主要的后台业务也快半年了,现在发现刚接手的时候由于 缺乏经验,总感觉别人的技术高深莫测。但是,自己接触久了才知道里面有这样那样不完善 的地方。还存在一些设计缺陷,非常严重的缺陷都有。比如说把 Redis 当作数据库使用, 说白了就是没有设置缓存过期时间,然后 Redis 里面的数据又很重要不能丢失。这对服务 里面还存在着一些缺乏文档、维护人员早已离职的老旧服务,这些老旧服务基本上平时无法 来仔细研究。但这些服务中总有那么一两个依然在被访问,或者早就不能访问了,客户这几 天突然发现然后急着催我们解决。很多安全问题也需要整改,某几个 API 账号有一些高位 的权限需要收敛,但是我并不知道这个几个账号具体是做什么的。这些问题,就是日常频繁 遇到的,很难避免。

2023年11月15日

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

本周我发现有些服务框架写得并不是很好,特别是某些 Java 框架。当 CPU 占用达到 40% 左右的时候,就会出现大量的超时现象。这些服务,CPU 核心和内存容量不是不多,工作线 程数目也不是不够。但是,就是跑不满 CPU。用 Java 性能工具分析发现,其实发现大部分 工作线程处于 Idel 或者 Waiting 状态。目前综合所有情况分析,还是百思不得其解。NIO 也用上了,还用的是 Netty 框架,但是吞吐量就是上不去。通过对于线程的分析,发现并 没有特别繁忙的业务线程。推断应该是 IO 或者某种等待机制导致了这种低处理效率。 这 次我是准备通过削减节点数量来降本,在执行前并没有考虑特别多的因素。所以我在削减节 点容量的时候,只是通过查看 CPU 的负载来判断节点是否能够承受。当我将北京地域的 workload 平均 CPU 占用率提升到 35%-40%的时候,整个服务出现了大量的超时现象。当时 把我吓了一跳。后面从监控上分析,几乎是整个背景地域的节点都超时了,也就是处于一 种”休克“状态。 本周也继续重构某一个老的 PHP 网关服务,新的网关用 Java 写成。但是我其实不是很赞 成网关用 Java,毕竟 Java 这种语言的执行特性很大程度上决定了它不适合特别高的并 发。并且,我们目前所用的都是 JDK 8,并没有引入轻量级的线程,每个 4c8g 容器内线程 最多也就 800 个,多了线程切换开销就会特别大。所以单个容器的吞吐量有限,承载同样 的流量需要更多的容器。刚开始我是用 Go 重写的,这个框架很不错而且也有专门的团队维 护,但是 leader 还是让我用部门自研的那个 Java 框架。可能人事上的考虑居多吧,无 奈,我还是先写吧。

2023年11月15日

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

本周主要处理一个节前发现的风险项。某个服务,在使用 Redis 的时候并没有为键设置 TTL,而是寄希望于 redis 的淘汰策略。我看这个服务使用的 Redis 设置了 LRU 淘汰策 略。这种策略看似比较完美,但是当某一较短时间段内,写入流量很大的时候,存在隐患。 这个时候 Redis 会触发淘汰过程,并集中尽力于此,以求能腾出足够的空间。这意味 着,Redis 不能很好地执行查询等正常操作了。这会造成业务层对 Redis 的读写延时均出 现剧烈波动。到现在,我已经遇到过 2 次这样的问题了。而且不设置 TTL,Redis 一直处 于 100%的使用率状态,我们从容量上并不能获知按照现在的业务量配置多少购买容量足 够。业务低峰时期是否能够通过缩容降低成本也是不能确定的。所以,为 Redis 留出一个 足够的可用空间是十分重要的。 所以,我需要改造这个服务,使之对每个写入的新键设置 TTL。按照 Redis 的逻辑,如果 键在写入的时候已经存在,则也会被设置一个 TTL。为了业务稳定,我不求所有的键都设置 TTL。所以舍弃了在遍历 Redis 寻找未设置 TTL 的键的方案。其实我调研过这个方案,在 遍历的时候,使用需要游标来做。设置的 TTL 长短也有讲究,为了防止同一时间大量键过 期造成延时的大波动,设置 TTL 的时候在基础时长后加入了随机时长。假设基础时长为 T,那么加入随机时长后,TTL 的长度会处于 T ~ 2T 之间。这意味着,要求存留时间越长 的键,过期的分布也越广。这能够确保业务延时更加平稳,防止数据库压力过大。

2023年6月21日

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

从一月底到二月初,都属于春节的范畴。期间,负责保障春节阶段的运行,需要随时待命处 理线上问题。我一直处于一种担忧的状态,好在线上问题并没有主动找上门。整个春节期间 维持整体不动是最好的。 这周我在评估一个重要的需求,所带来的影响。我认为,对于一 个新的业务需求,特别是应用于一个复杂的业务系统,需要考虑多方面的影响。如果这个时 候,对这个系统并不是特别熟悉,经验不多的话,最好选择做最小改动。这不是保守,而是 将影响控制在你可以想象地到的地方。因为你不会知道在什么地方,某个违背直觉的机制在 运行重要的业务逻辑。得出这样的结论,并不是出于我的想象。 这篇文章写于半年后,到我写这篇文章的时候,我已经遇到至少两次这样的事情了。当时我 对某个服务做出了大刀阔斧的调整,在调整完的时候,一切正常。发布后,也看似正常。直 到若干星期之后,我偶然发现了某个串联上下游的机制,它差点受到我的影响。在承接一个 业务系统的时候,很大概率它是转接和很多手的,藏有很多你不知道的历史,所以框架、核 心逻辑能不动就不动。 然后,本周彻底解决了针对一个安全加密服务加密接口不兼容中文 的问题。主要的问题是,它将加密的原文直接作为 Redis 的 Key。当原文中存在中文,会 发现虽然 Key 存储了,但是找不到。我的方案是在存储的时候,Key 的内容不要直接包含 任何业务原文,先取哈希。这样既能够避免一些编码、兼容的问题,也可以大大提升安全 性。

2023年6月21日