当前位置: 首页 > 网络应用技术

如何进行“防御性编码”?

时间:2023-03-05 17:35:50 网络应用技术

  简介:类似于“防御性驾驶”对驾驶安全的重要性,总结防御性编码的目的:在Buds中消除了一件代码质量。要实现“防御性编码”,我们需要完全认识代码的质量,也就是说:“一旦您认为这个地方可能存在问题,那么它将(在某个时刻)。”当然,实际情况比这种情况更为严重。每个人的编码经验,每个人的意识的界限都是不同的,并且潜伏在意识界之外的“危险”更加隐蔽和无与伦比。在意识的水平上,我们必须放弃“将其视为理所当然的想法“和“几乎”,认真评估这些问题的可能性,并认真对待这些风险。但是,如果主题在这里停止,它实际上缺乏执行级别的指导意义,它将无法刺激“连锁”。本文的目的更关注“实践层面”的指导

  作者|单词白色

  来源|阿里的开发人员公共帐户

  与“防御性驾驶”在驾驶安全方面的重要性类似,总结了防御性编码的目的:消除了一件代码质量。要实现“防御性编码”,我们必须完全认识到代码质量的严肃性,也就是说:“一旦您认为这个地方可能有问题,那么它将(在某个时刻)。”当然,实际情况比这更严重。每个人意识的界限都不同,并且潜伏在意识边界之外的“危险”更加隐蔽和无与伦比。

  当然,从意识层面上,我们必须放弃“理所当然的”和“几乎”的想法,认真评估这些问题的可能性,并认真对待这些风险。但是,如果话题停在这里,实际上将缺乏执行级别的指导意义,它将无法刺激“连锁”。

  本文的目的更关注“实践层面”的指导。

  以下方面需要更多地关注我的习惯和观察结果,并使用伪代码例如示例。

  欢迎大家在评论区域分享他们的“防御性编码经验”。

  1并发冲突

  在实际项目中,错误的比例很高。它具有多种外部表达形式,但关键点是:“当您的代码同时称为时,它将如何执行?”

  我们心中必须有一个运行时的世界观。运行的上下文是这样的:多线程 - > multi -process-> multi -machine-> multi -cluster。当我们编码时,我们必须完全考虑上述世界中代码的汇编的可能性以及相应的潜在后果以及相应的潜在后果。

  以一些具体的问题示例):

  示例1:

  线程A和线程B的两个线程需要更新“相同的”数据,这将发生在这种情况下:

  1.线程更新数据库(x = 1)

  2.线程B更新数据库(x = 2)

  3.线程B更新缓存(x = 2)

  4.线程更新缓存(x = 1)

  最后,x的值在缓存中为1,数据库中的值为2,这是不一致的。

  示例2:

  如果HSF和HTTP同时调用此代码,则会出现问题。

  示例3:

  在一个系统中,有两种价格类型的大小,商业逻辑需要小 <= large,且 small 和 large 有2个入口可以分别修改。

  目前方案是:对要改变的small或large,增加上面大小关系校验,不通过则拦截,例如 改动small的入口上,校验改后的small <= 系统里的large,不通过则不允许修改。

  假如,最新需求要求:修改large的入口继续拦截,但修改small的入口不再拦截,而是发现如果改后small > 大部分系统将继续进行大型系统=修改后的小+0.1,以便将继续建立约束关系。此更改是否存在问题?

  答案:此更改存在问题,也就是说,小的价格类型具有两个链接可以同时修改,这也是并发问题。

  对于特定示例:

  在执行架子 - 独立环境中执行此示例时,没有问题,但是如果在线群集上有许多节点,则发送频率的控制不正确。

  如果此代码同时由多个请求执行。

  单个任务触发器不是并发问题。

  但是,由于执行时间较长,两个执行时间重叠。

  2个交易问题

  对于第一个A,然后是B和C的这种组合,请仔细考虑确保一致性的必要性,并评估是否进行交易保护。

  需要交易:对于一组操作组合,有必要确保执行顺序,确保上下文的一致性并确保结果的一致性。

  这个问题的可能性不高,更容易解决。

  但是,应注意的是,交易的执行不应太长,并避免死亡锁的问题。

  上述上传和处理Excel文件作为示例。如果实现了,则将其分为两个步骤:

  1.调用后端API并将文件上传到服务器的临时目录。

  2.上载前端时,请致电另一个API,以通知该文件的背部端端处理。

  此示例将发生在概率成功或失败的集群环境中。群集节点的数量越高,失败的可能性越高。这是因为前端请求在之前和之后都调用不同的节点,并且上下文的发生方式不同。

  例如,对于ECS运行状态的定时消息,如果下游消费者不消耗顺序,而是消耗并行消耗,则最终记录的状态与实际情况不一致。

  3个分布式锁问题

  分布式锁通常每天都使用,并且有些盲点在使用细节上很容易忽略。

  1.是阻止等待锁定的阻塞,还是等待锁重复锁,还是无法直接返回锁。

  在这个级别上,主要考虑点是此呼叫链接需要时间和成功。

  例如,上游是用户的操作,不得将其封锁很长时间,等待锁定太久。

  2.锁的关键设计至关重要。

  合理的设计锁定键可以减少锁相撞的可能性。

  例如,是将您的锁定添加到BU级别还是将其添加到某人中,冲突的可能性显然大不相同。

  3.对于持久锁,在周期内执行业务逻辑时,请检查状态检查。

  4.可以使用无全球锁的本地锁。

  1.合理设置锁定TTL,结合您自己的业务场景以做出选择

  例如,锁定后执行大量数据的批次计算场景。

  如果锁定TTL太长,则计算被异常(例如机器重新启动)中断,则该长TTL无法通过其他节点/线程获得。但是,如果TTL设置太短,则可能不会等待完成执行。锁被偶然地取走。

  2.注意监督机制

  将会有像Redisson这样的锁观察人员,它超过了设置或默认时间,锁定被秘密释放。

  1.在必要的情况下,避免强行释放锁,检查锁定者是否是他本人。

  2.对于没有TTL的锁,请考虑在极端情况下该过程的锁定状态(强行杀死和机器重新启动)的锁定状态管理。否则,一旦事故出现,锁将始终丢失。

  4缓存问题

  没有缓存和数据库的数据,但是大量要求它们,导致DB压力过大。

  通用解决方案:也可以缓存空值,但是TTL设置相对较短。

  通常,缓存热点密钥到期。目前,大量请求通过缓存击中了DB,导致DB压力过大。

  通用解决方案:当缓存查询失误时,设置一个互斥锁,仅允许请求真实请求db并重写缓存,以避免大量请求涌入。

  缓存中的大量数据在短时间内到期。从基因上讲,流量流动,缓存创建时间接近TTL。

  常见解决方案:它不是TTL设置中的刀,而是随机漂浮在合理范围内的刀具,以避免缓存浓度故障。

  通常,一致性要求不会非常严格。但是,如果需要强大的一致性保证,则必须考虑缓存和数据库之间的数据。

  一个可能的解决方案:编写DB时编写缓存,并且在阅读数据库操作时不要编写缓存。DB和CACHE的写作操作应锁定以避免并发问题。特定过程如下:

  当DB请求发生时:

  1.删除缓存。这次,读取操作缓存将被错过,并且读取db中的旧值。

  2.写入db。这次,读取操作缓存将被错过,并且读取了db中的新值。

  3.编写缓存。这次,读取操作缓存将被击中并读取缓存中的新值(与DB的新值一致)。

  必须注意的是:

  1.用于数据库的所有数据记录的缓存,这可能会导致高缓存空间职业,但实际利用率不高。

  2.如果缓存键是热点或大型流量相对较大,尽管缓存“删除 - 螺纹”间隔很短,但它仍可能导致缓存中断。

  3.如果缓存写失败,则需要书写相应的补偿机制,有必要注意与其他正常写作的薪酬写作的冲突和时机。

  这本身并不是问题,但是较低的命中率表明缓存的设计或使用存在问题,并且需要重新设计。

  如果特定缓存节点的CPU的使用量比其他节点高得多,则意味着可能存在热点。这次,需要拆卸缓存密钥以进一步分散流量。

  5故障处理问题

  

  处理方法:

  1.故障转移。立即重试。

  2.故障。记录失败,后部处理。

  3. Failfast.Failure直接返回异常。

  4. failsafe.ignore失败,继续该过程。

  这不是选择这种处理,而是与“清醒的头脑”相结合做出选择。

  在某些情况下,我们将设置一些默认值,默认状态等。在初始化等时,我们必须完全考虑出现异常时是否存在风险。

  例如,在开始时,该代码是在Kaicheng时配置的,但是该状态没有使用业务操作过程打开,也就是说,没有办法及时更新它。

  随着时间的流逝,新城市的发展可能会引起问题。

  6开关配置问题

  当开关发布时,不同的批次将具有时间间隔,并且大多数场景都可以忍受此时间间隔。但是,在某些情况下,它可能会导致数据不一致之类的问题。

  使用开关时,您需要提前考虑此问题。如果无法容忍这种情况,则需要更换其他解决方案。

  Switch的逻辑是:

  1. Switch将默认情况下记录代码中的默认值。目前不是耐用的值。

  2.修改代码中的无声值时,交换平台还显示代码的默认值。此时,它不是耐用的值。

  3.仅修改值并成功地推动交换平台上的值,Swith平台将节省耐用的值。

  4.开关保留耐用值后,无论代码对默认值修改还是删除@AppSwitch配置,都存在耐用性值。

  如果您在开关平台上看到开关值,则认为它是耐用的,然后在代码中删除默认值,这也可能导致故障。

  在进行代码结构重建时,如果没有开关名称空间,它将导致持续的开关您按下推动,这将导致严重的在线失败。

  关于发现应用程序级服务和接口级服务与Dubbo Ecology解决方案之间的差异,我不会在本文中详细介绍。

  简而言之,应用服务发现要求开发人员在接口外关注应用程序名称,并且注册中心的冗余信息较少。接口 - 级服务发现开发人员只需要引入接口名称,但是将注册中心的冗余信息与比较的人进行了比较。

  Switch提供了一种简单易用的配置能力,但请勿将应该考虑的问题抛弃并正常在开关上处理为开关。隐藏。

  7重大风险评估和处置

  为了开发需求,我们需要评估风险和可负担性。主要目的是防止重大失败,而不是防止所有错误。

  没有固定的风险处置标准。我建议评估风险的可能性和潜在问题的严重性与业务情况结合,最后制定相应的解决方案。例如,如果您发现损坏的风险,则需要使用所有手段都可以阻止漏洞;但是,如果这只是错过指甲通知的很小概率,则可以增加相应的警报。

  我们如何评估主要风险?我建议在几个链接中评估:

  1.整理关键业务流程。

  2.整理每个业务流的关键链接。

  3.整理每个密钥链接的上游和下游的密钥逻辑和密钥。

  4.结合自己的场景,假设关键逻辑和极端问题在上游和下游发生。

  有必要在这里强调,并非所有模块都需要采用非常极端的条件,并全面判断与其实际的业务需求和历史风险相结合。

  另一个示例:

  假设有一个由用户资助的传输系统,用户可以通过应用程序执行交叉银行转移操作。

  然后,该系统必须考虑到诸如超时和转移失败之类的场景。在考虑转移时间或失败时,失败效果是好的,还是好 - 良好?

  此外,有必要考虑应用程序端的用户交互设计。如果遇到网络中断或超时,并且用户看不到任何问题提示,则用户可能会再次启动转移尝试并最终转移这两个钱。

  这个评估过程看起来更长一些,但实际上,对于那些了解其系统和需求细节的人来说应该很容易做到。如果您不能这样做,您只能加强对细节的理解和学习。

  以研发学生为中心,向内看:我们需要不断提高防御性编码的意识和实践能力;注意:外部环境需要尽可能地提供它。

  例如,在面对紧急截止日期的需求时,防御法规的执行将在一定程度上受到影响。

  再次欢迎每个人留言。

  原始链接

  本文是阿里巴巴云的原始内容,未经许可就无法重印。

  原始:https://juejin.cn/post/7095932514019049486