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

看了一下当时的周报,值得说的事情也就是关于日志整理的事情,然后就是关于一个配置下发服务的多地部署。关于多地部署的事情,下一篇技术周报再详细说明。 首先来说说,日志方面的事情。现在的话,线上有很多服务。然后目前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等等,这个我来手动分配,最后会把分配方案写到文档中。后面的数字,都是随机生成的。 注意,这里采用随机生成的方式,在每天这么大量的日志的情况下,可能产生重复。但我评估了一下,这种重复对我们定位问题这个需求来说,无关痛痒。所以索性重复就重复,再重复15分钟之内重复的概率也非常小,可以不考虑。最后,生成的TraceID都是固定位数,以99开头的数字。 在业务处理请求的过程中,会不断有日志输出,采用异步化的方式输出到日志盘中的目标文件中。这个日志输出的格式和分类需要强制统一,包含下面的内容:时间、日志等级、TraceID、对应线程ID与日志内容。这些都用“|”隔开来。然后,需要把不同等级的日志区分开来存储。后续腾讯云CLS会通过规则来监控这些日志文件的修改,然后采集这些输出的日志,通过上面的统一格式和区分等级,能更简单地统一各个服务的采集配置,并避免把一些低等级的日志采集上去。低等级的日志可以留在日志盘中,如果研发人员需要看,就可以登录到容器中来看。毕竟,相比于被CLS采集后,附带产生的流量、计算和存储的成本,日志盘的存储成本还是能接受。这样也算是通过差异化地方式提供服务来降低成本。后续,对于不是那么高频用到日志,只需要通过降低其日志等级来避免被采集,从而降低成本。值得一提的是,被采集上去的高等级日志,只会按照TraceID索引,而不再索引内容,这样可以非常有效地降低成本。我称这些日志为业务日志,这些日志在CLS上只能通过TraceID来找,而不必通过内容来查找。 在响应拦截器中,我们需要一次性向日志输出这个请求的简要信息,也就是Access日志,包括请求的字段,响应的字段,还有处理时间,容器的IP地址,TraceID,服务名称等等内容。Access日志将单独输出的access.log中,然后这个文件也将被CLS采集。CLS会按照其中最关键的字段(请求字段,响应字段,TraceID,服务名称)索引这些日志,后续我们通过模糊搜索请求字段或者响应字段就能定位到具体的一次业务请求了,我们可以对这个请求有直观地了解,然后也能拿到TraceID。通过TraceID,我们可以用CLS一次性找出与这次请求相关的所有业务日志,摸清楚这次业务请求的来龙去脉。 通过上面对于业务日志和Access日志的方案的设置,我能够避免让CLS建立全文索引(索引日志的所有内容),从而大幅度降低成本。虽然没有了全文索引,但是研发人员通过BUG单中的用户给出的信息,搜索请求或者响应中的对应字段,来查出TraceID,后续也能快速且准确找出所有有关的日志来。这实现了成本和易用性的一种平衡。还有一个没有提到的,就是被CLS采集上去的日志,就没必要在日志盘中长期存在了,可以设置滚动规则,1-2小时后就删除掉。 对于Node.js技术栈的服务,我也基本上采取相同的思路,通过框架提供的context能力,来处理TraceID。日志的输出和采集方案,也严格遵守Java服务的规范。然后还有C++技术栈的服务,那就麻烦很多,这些服务非常老了,但依然处于关键位置上。但我没时间处理这些了,这些服务出问题的概率非常小,先放着。 通过这样的处理,CLS上对每个服务配置日志文件的采集规则都是统一的了,大大简化了配置的繁琐程度。对于每一个服务,我们只需要复制粘贴现有的采集和索引规则即可,因为这些服务的日志格式和输出都是呈现形式都是相同的了。这样处理下来,有一个月的时间。完成后,日志这块确实都规范起来了,查问题也变得方便了。我也能对现有日志进行归类,看看哪些业务日志对查问题真正有效,哪些并没有太大的帮助。后续我再带着另一个同事,削减了一大批以前没发现的无效或者重复的日志,又节省了一大笔费用。具体地,我们两个经过评估分析发现每天可减少121亿条无效与重复的日志,削减现有成本的38%。

十一月 17, 2024

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

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

四月 15, 2024

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

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

四月 15, 2024

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

本周相关的一些事项,我认为最值得讲得就是某个数据库接入层服务的给每一条数据加上键过期时间。这个数据库接入层是一个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一直处于满的状态。 为了降低风险,最好就是为每一条数据设置过期时间。使得Redis中存储的始终是较热的数据。而设置过期时间,采取的策略是取一个固定的时间Y,然后再生成一个均匀分布的浮点随机数因子a,从0-1变化。然后我们对于某一条数据的过期时间就是Y+aY。这样设置能尽可能避免某些情况下大量数据集中过期造成的某段时间内数据库压力过大问题。 当时Redis中有64GB的数据,且整个Redis实例是满的。我评估了一下,如果只是增量进行数据更新,确保新的数据是设置了过期时间的,这样应该能让存储空间缓慢降下来。最终应该是会剩下少量的数据没有更新,占比应该不大,因为我们的业务不是存量业务,大部分用户应该会继续使用。 2024年4月 将近一年多时间观察下来,Redis中的数据只剩下了10GB左右。而Redis实例的规格我也在半年前降下来了,成本有所削减。

四月 14, 2024

关于个人对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美元!我很惊讶钱都花在哪里了? 我在控制台上查找,又在Cost Explorer上进行了进一步的查询,才发现系统盘和数据盘的费用都按照我申请的规格计算了!没想到,AWS竟然是按照服务器和云硬盘分别计算价格的。而且,我还不知道如何配置第二代SSD,虽然性能很好,但我用不到这么高的性能,而且价格比第三代SSD贵20%!还有,几个快照居然每个月费用超过了7美元。而且,费用查找界面非常复杂,一个选项下可能有很多项目,需要一个一个取消勾选,慢慢查找到底是哪一项花费了多少钱。 因此,从2022年12月开始,我开始不断削减成本。我逐渐取消了快照备份,然后取消了数据盘,最后还设法减小了系统盘的容量。系统盘容量扩容很方便,可以一键操作,但缩减容量非常麻烦,需要手动操作。在网上找了一圈,发现AWS有个名为Lightsail的个人用户友好产品,我感到后悔了。 我还发现DNS托管每个域名每个月要0.5美元!而且,根据DNS访问次数还会额外计费。特别是域名内有多个CNAME跳转的DNS配置风格,一次访问会被计算多次解析次数。如果不想被计费,就需要设置Alias。虽然DNS功能很强大,但我这种个人用户用不上,而且好像Cloudflare的DNS更快而且免费。当时,我还把两个域名迁移到了AWS,并且都托管了。因此,这块DNS每个月花费大约1.5美元左右。最后,我想了想,还是把DNS解析迁移到了Cloudflare。值得一提的是,AWS的域名续费很贵,每个月com域名要花费12+美元,而Cloudflare大约是7美元左右。这也是一笔支出,几乎相当于一个月的EC2费用。对于个人用户来说,实在不划算。 总结一下,AWS提供了很多功能和选项,但对于个人用户来说,有些功能可能用不上,而且费用也会让人感到意外。因此,在使用之前,一定要仔细了解每个选项的费用计算方式,以免产生不必要的额外费用。 从2024年2月开始,AWS开始对公网IPv4地址收费,无论是弹性IP还是绑定到EC2上。根据计算,每月大约需要花费约3美元。考虑到这并不划算,我决定移除公网IPv4地址,将服务器改为纯IPv6。这样一来,我的网站基本上无法直接访问了,因为在很多情况下,对IPv6的支持并不完善。我尝试了AWS CloudFront的解决方案,但发现CloudFront的源站访问不支持IPv6,也不支持内网的IPv4地址。我思前想后,好像没有解决方案了?(实际上,可以将域名托管到Cloudflare,然后开启代理) 综上所述,作为个人用户,我不推荐使用EC2等产品。如果必须使用,建议选择类似Lightsail这样的产品,计费较为简单,而且不会在不知不觉中开启"增值服务",账单无法控制。而且最好使用用AWS托管比较轻量的静态网站,用Lambda、Amplify、S3这类产品,按量付费才是划算的。对于域名和DNS方面,个人用户推荐选择像Cloudflare这样的服务提供商,因为它们免费且性能很好,而且域名续费也不贵。我认为AWS的产品更适合企业等大型用户,这类用户可以协商价格,相比我们按照官网给出的标价会更划算。最后需要提醒的是,AWS的技术服务需要额外付费! 当然,AWS也有它的优势。我的1核心(平均性能为20%)的机器能够运行很多服务,并且响应速度也很快。一年下来也非常稳定,几乎没有出现过什么问题。另外,像CloudFront这样的加速服务,每个月的免费额度居然有1TB,而且还是永久的。因此,我将我一些速度敏感的站点都配置在AWS的CloudFront上,一年来发现速度稳定且响应快速。只是配置有些复杂,需要一些基础的WEB技术知识。 现状 目前,我将我的博客网站和RSS订阅等动态网站迁移到了专业的网站托管服务商。我选择了一个可靠的服务商,他们提供了稳定的运行环境和安全性保障。不仅如此,我还趁着最近的黑色星期五促销,以非常优惠的价格购买了几年的服务,这样我可以放心地运行我的网站。这家网站托管服务商还提供了许多附加服务,比如邮件服务,让我可以方便地与读者进行沟通。他们还提供了缓存加速功能,可以提升网站的加载速度,让访问者有更好的体验。另外,他们还提供了域名注册服务,让我可以方便地管理我的域名。管理界面也非常简单易用,让我可以轻松地进行网站的管理和维护。对于个人用户来说,这样的服务就足够了。 除了博客网站,我还有一些个人开源项目使用的静态网站需要托管。为了简化操作,我选择了Netifly作为静态网站托管平台。Netifly提供了简单易用的界面,让我可以快速部署和管理我的静态网站。他们的服务非常可靠,我可以放心地将我的项目托管在他们的平台上。 另外,为了进行后端开发和测试,我计划使用AWS的EC2来部署一些个人开发的后端程序。EC2是一个强大的云计算服务,可以提供稳定的运行环境和高性能的计算资源。我还在EC2上配置了公网IPv6地址,为将来使用Cloudflare代理做准备。使用Cloudflare代理可以避开IPV4公网收费的问题。 总的来说,我对我目前的网站的迁移和托管选择感到非常满意。我相信在接下来的几年里,我的网站将能够稳定运行,并为读者提供更好的体验。 总结 在使用AWS服务一年后,我有以下几点个人感受和总结: 我选择使用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对于个人用户来说费用较高且某些功能可能用不上。在使用之前,一定要仔细了解每个选项的费用计算方式,以免产生不必要的额外费用。

十一月 24, 2023

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

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

十一月 15, 2023

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

本周我发现有些服务框架写得并不是很好,特别是某些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框架。可能人事上的考虑居多吧,无奈,我还是先写吧。

十一月 15, 2023

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

本周主要处理一个节前发现的风险项。某个服务,在使用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之间。这意味着,要求存留时间越长的键,过期的分布也越广。这能够确保业务延时更加平稳,防止数据库压力过大。

六月 21, 2023

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

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

六月 21, 2023

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

这一周主要是确保在春节前各类服务的稳定。近期发现某个服务经常在流量高峰时段报超时,我提醒转交服务负责人处理。但是几天过去,服务负责人依然无法说明原因。只好亲自处理这个问题,因为报警已经十分严重,部分节点超时率能够达到20%。在这段时间中,应该是由于临近假期,流量大幅度上涨,12月底相比已经上涨了100%。所以首先是怀疑服务的承载力不足,所以先进行了一次扩容。但是,扩容后并未解决此问题,告警频率和超时率基本未变化。这种情况下,对服务的源代码进行分析后发现,该服务的接口会首先调用一个下游服务,然后再异步地向数据库中插入数据。因为异步操作并不阻塞工作线程,所以首先应该怀疑是这个下游调用的问题(实际上,我在数据库那边纠结了很久)。 从终端登录进一个节点检查日志,发现所有下游调用走的居然是同一个IP和端口,并且走的是稻草人节点。所以我先将该服务做上云操作,排除稻草人转发损失造成的影响。上云完成后,刚切流量的时候发现某个地域(不是原先的地域)下有大量超时产生,做了扩容后也无解,十分疑惑,所以暂时切回来了。我非常奇怪,为什么上云之后,超时率却变得更高了。从云上监控分析问题,发现在切流量后,下游服务的某个云上节点CPU占用非常高,而其他的节点却很低。开始怀疑是负载均衡的问题,所以又回去检查服务的源代码。果然,该服务从注册中心拿到下游服务可用节点列表后,只会调用列表中的第一项。所以某个地域的几乎所有流量都集中在下有服务的某一个节点上了。而原先通过稻草人转发的时候,稻草人调用云上节点的时候会做一次负载均衡。所以,最初的问题应该是流量大量上涨,该服务却没有负载均衡,所以导致某个稻草人过载,进而导致超时。稻草人是代理节点本身没有逻辑,所以能够处理的并发数更大,超时问题不是很明显。而一旦上云,流量会直接集中在某个业务节点上,大量流量一齐涌入,会瞬间导致该业务节点大批量超时。 有了上述的论断后,回去找稻草人的监控,发现某个稻草人确实已经过载了,CPU占用居然达到了95以上。知道原因后,接下来继续上云,先最大限度消除超时告警,然后再改代码添加随机负载均衡算法。这样做是因为节前修改代码,流程上会比较麻烦。所以首先,我加倍了下游服务的单个节点的核数,这大大增加处理能力。然后,做切流量的操作,一个下游节点的CPU占用数突然升高,最终一直保持在比其他节点高很多的比例上,这是意料之中的事情。当整个服务平稳下来后,上云就成功了,这个时候告警已经消除,超时率归零。接着,就是修改改服务的代码,添加负载均衡的机制,在各个流程审批通过后为该服务发布新的代码版本。 在我发布完新版本后,下游服务的各个节点的CPU占用就接近了,最终问题解决。

二月 14, 2023