巩固系统韧性三个基础策略

巩固,系统,韧性,三个,基础,策略 · 浏览次数 : 0

小编点评

## 疑难杂症:从解决问题到确保业务正常 疑难杂症就像隐蔽的毒针,在我们的工作背后默默地在隐约地针刺我们。当面对这些看似无意的问题时,我们似乎只能尽力用粗暴的方法来解决,却忽略了根本原因,导致问题再次出现。 **疑难杂症的本质:** * 它们并非由代码导致,而是与运维错误有关。 * 它们是无法主动解决的问题,无法从根源上得到解决。 * 它们往往是由于系统资源有限导致的,导致意外状况。 **解决疑难杂症的重要性:** * 它们提醒我们,要关注运维错误的出现。 * 它们让我们意识到,解决问题的关键并非只在于编写代码,更在于确保系统能够处理意外情况。 * 它们促使我们探索更加稳健的技术方案,提高系统韧性。 **解决疑难杂症的策略:** * **提升系统资源:**优化代码,减少数据库连接数,提高网络性能等。 * **监控系统日志:**及时发现异常现象,以便进行及时处理。 * **采用容错技术:**实现对异常情况的处理,减少问题发生的次数。 * **使用可扩展的技术:**选择能够自动扩展处理的技术方案,比如分布式计算框架。 * **进行代码审计:**分析代码,识别潜在的错误并进行修复。 * **记录问题解决过程:**记录问题的解决方案步骤,以便在遇到类似问题时参考。 **结论:** 疑难杂症是运维中一个重要而不可忽视的问题。通过有效的解决方法,我们可以提升系统的稳定性和韧性,为业务正常发展奠定基础。

正文

众所周知我所在的团队常年解决线上问题,我也以为我们会在解决一个个具体问题的道路上无聊走到黑。但是最近出现的各种疑难杂症似乎让我们的工作有了一点乐趣,甚至有了更高级的意义。

这些疑难杂症包括但不限于

  • 因为网络故障导致邮件发送失败
  • 因为数据库磁盘满导致数据出现了读写不一致
  • 事件推送出现了延迟
  • 因为死锁导致异步任务频繁挂掉

它们有两个特征:

  1. 至少从已知的排查结果看,它们并非是由代码导致。我将它们归纳为运维错误(operational errors)——文章《Error Handling in Node.js》里对将错误划分为程序错误(programmer errors)和运维错误(operational errors),简单来说前者由代码产生,可以通过修改代码避免;后者与程序无关
  2. 与程序无关,也就意味着无法主动解决

如果我们的工作职责是解决问题,而问题却无法得到解决该怎么办?——仅仅用“解决问题”来形容我们的工作并不恰当。更准确的说,我们是在确保业务部门工作的正常推进,而扫除推进过程中的障碍,并不一定要从根源上解决技术问题。

换而言之,当我们面临掌控之外的不确定因素时,与其与难题死磕,更优的选择是让系统变得富有韧性(resilience),即能够迅速让服务从意外状况中恢复过来。高级精密的技术可以是我们的选项之一,但与其事后花大力气补救,有一些改进手段可以在日常开发过程中用低成本的方式与之融合,这些就是本篇要聊的内容,也是标题中强调“基本”这个词的原因

快速失败

我们发现有一类空指针问题是由磁盘空间不足之后数据存储失败导致的,比如下面的伪代码

var authorName = book.author.name

在磁盘空间不足的情况下,book 信息会存储成功,但 author 并不会。而开发者在编写代码以及在设计数据库时又从未考虑到 author 不可空的情况。于是在磁盘问题发生之后,上述语句就会引发空指针问题,因为 name 所依赖的 author 并不存在。

此时想当然面临一个最简单粗暴的解决办法,让代码中的 author 对象变得可空(nullable type):

var authorName = book.author?.name

这么做可能会让情况变的更加糟糕,因为它忽略了错误在分布式系统下的传播性:虽然此处的空指针问题得以修复,但是为空的 authorName 是否符合下游代码的期待?如果不是,下游代码是否会发生更加严重的错误?经验告诉我们可怕的不是错误,是未知。也许这个错误会导致无法释放资源进而耗尽内存或者连接池,而此时你又无法遇见到它,那么可空的修复方案带来的破坏则会更广。

退一步说即使空 authorName 变量的不足以对代码造成运行中断的危害,但是当用户在页面上发现这一异常行为上报时,我们的排查工作会因为兼容可空变得困难,因为从监控上看没有任何与此有关的错误日志产生。

按照过往的经验,通过监控系统发现错误的效率会高于等待用户上报;同时越早暴露错误也越能够让损害和修复成本降到最少,基于此,让错误尽快发生似乎是一个更优解。

甚至允许系统彻底挂掉(crash it)也是快速失败的一种,当然前提是

  • 程序能够及时得到重启
  • 客户端能够有重连的能力

备选方法(Fallback)

还有一类看上去稍稍不那么糟糕的情况是:我预见到了问题的发生,于是我用分支语句准备好了一个备选方案,似乎就可以高枕无忧了?比如下面这段伪代码

if (redis_is_health) {
    const book =  getFromCache()
} else {
    const book = getFromDatabaes()
}

这则分支试图处理 Redis 不可用的情况,但遗憾的是这类代码依然充满风险。

首先如何能够测试到边缘分支情况?针对 Redis 也许我们可以通过修改 connectionString 让它变得暂时无法访问。但是对于掌控之外的基础设施,甚至更加极端的情况,比如磁盘耗尽问题,我们是无法模拟出完全一致的场景的。又例如在解决文章开头提到的随机死锁问题,为了能够测试到分支代码,我们特意加入了一段“破坏”代码,很显然这种测试方式经不起推敲。

再者因为 fallback 并没有机会在线上环境实际运行,自然并未有人见识到它真正的功效,那么当它实际被启动的那一天,带来的可能是危害而不是帮助。上面有关 Redis 的代码时来自 Amazon 的一个实际例子,当 Redis 不可用的那一天真的来临时,因为承载了过多请求,数据库成为了各个服务的瓶颈,导致网站直接挂掉。

专注于提高主线代码的可靠性会带来更大的收益

重试(Retry)

我们要想通两件事:

  • 失败一定会发生:"Failures are a given and everything will eventually fail over time"(Dr Werner Vogels CTO of Amazon)
  • 和解决性能问题类似,想要正式处理这些失败,一定是去中心化的:需要分策略的解决不同问题;甚至对于同一个问题,不同调用方处理的方式也不会相同

但千人千面的线上问题并不意味着束手无策。例如当依赖的系统/服务/网络不可用时,简单粗暴且有效的办法就是不断重试。因为你需要想通的第三件事是,它必须要恢复上线,且终会恢复上线。

不要小看了重试,我愿称之为性价比最高的解决手段,因为我们所用到的重要前端类库、后端组件都天然集成有重试机制,我们要做的就是将他们利用起来。

即使手动实现大部分情况技术也并不复杂,这里就不赘述了。但是请千万留意重试策略,切忌无脑向下游发送请求,这样与 DDOS 攻击无异。也会带来不可知的副作用(我们最近便遭遇了一次由此引发的事故,有机会细聊)。具体请参考这篇文章:Timeouts, retries, and backoff with jitter

文化支持

Amazon 在 2019 年进行过一次有关如何打造韧性系统的分享:Amazon's approach to building resilient services,这轮分享中有一半的篇幅都在叙述组织文化在其中的作用,这与我们整个组的想法都不谋而合。

我们最大的苦恼是,线上问题被修复之后,同样的问题过一段时间又被爆出。也就是说如果没法把教训传达给功能开发的上游,甚至整个组织。就无法从根源上解决问题。

换而言之,学习能力对于富有韧性的系统来说非常重要,无论是对代码还是人而言都是如此。它也是我所认可的韧性系统的四基石之一

这种思维模式与 DevOps 类似:团队应该具有主人翁意识(ownship),完整的负责代码的生命周期,从开发到部署再到后期运维。Tech Leader 和 Principle 等类角色也应该对问题细节了如指掌,以免成为天马行空的架构师(non-practitioner architect)

反过来,如果 tech support 团队和 feature 团队隔离并且背负不同的 KPI,那么可以想象 tech support 最大心愿是产品一个季度上线一次才好,因为没有上线就意味着没有新的线上问题。

同时允许犯错也很重要,继续借用来自 AWS 分享中的一帧来展现通常解决问题过程中团队内每个人心理压力的变化

注意在最后的 Confirmation 阶段,开发者的心理压力会陡增形成一片 fear 区域,因为大家担心那段有问题的代码是我写出的,会被秋后算账。

“责备文化”形成带来的压抑感不言而喻,更重要的是它在扼杀改进和创新,因为改动的代码越多犯错的概率也就越大,那我何必自找苦吃呢。

与巩固系统韧性三个基础策略相似的内容:

巩固系统韧性三个基础策略

众所周知我所在的团队常年解决线上问题,我也以为我们会在解决一个个具体问题的道路上无聊走到黑。但是最近出现的各种疑难杂症似乎让我们的工作有了一点乐趣,甚至有了更高级的意义。

5.go语言函数提纲

1 本篇前瞻 前端时间的繁忙,未曾更新go语言系列。由于函数非常重要,为此将本篇往前提一提,另外补充一些有关go新版本前面遗漏的部分。 需要恭喜你的事情是本篇学完,go语言中基础部分已经学完一半,这意味着你可以使用go语言去解决大部分的Leetcode的题,为此后面的1篇,将带领大家去巩固go语言的

【后端面经-数据库】Redis详解——Redis基本概念和特点

Redis一直是后端面试的热门提问点,是“兵家必争之地”,本文是Redis详解系列的第一部分,介绍了Redis的基本概念,并辅以面试题进行知识点巩固。

pmp考试巩固知识点

1.冲刺评审会是需要相关的干系人参加的,在冲刺评审会上干系人可以审查并澄清角色、责任和管理模式2.采购中的争议,往往找合同和SOW,SOW是对需要采购的详细范围的描述,与供应商在可交付成果方面有争议时,可以查看SOW3.状态报告(工作绩效报告)是 PM 负责的4.冲刺本身就是一次共同作战,冲刺规划了

一个宁静祥和没有bug的下午和SqlSession的故事

1 背景 这是一个安静祥和没有bug的下午。作为一只菜鸡,时刻巩固一下基础还是很有必要的,如此的大好时机,就让我来学习学习mybatis如何使用。 这可和我看到的不一样啊,让我来看看项目里怎么写的。 我们项目中的Dao都继承于BaseDao,而BaseDao继承于SqlSessionDaoSuppo

MySql索引下推知识分享

作者:刘邓忠 Mysql 是大家最常用的数据库,下面为大家带来 mysql 索引下推知识点的分享,以便巩固 mysql 基础知识,如有错误,还请各位大佬们指正。 1 什么是索引下推 索引下推 (Index Condition Pushdown,索引条件下推,简称 ICP),是 MySQL5.6 版本

高性能MySQL实战(一):表结构

最近因需求改动新增了一些数据库表,但是在定义表结构时,具体列属性的选择有些不知其所以然,索引的添加也有遗漏和不规范的地方,所以我打算为创建一个高性能表的过程以实战的形式写一个专题,以此来学习和巩固这些知识。

解读与用户一起“跳动”的开源实时监控工具 HertzBeat

摘要:开源项目遇上华为云,会擦出怎样的火花? 在本期《开源实时监控工具HertzBeat如何与用户一起“跳动? 》的主题直播中,HertzBeat & TanCloud 创始人巩超与开发者和伙伴朋友们交流当前主流指标监控方案,解读HertzBeat及能力特点,并为大家演示了如何通过华为云商店安装部署