为什么你的程序总是出现 bug/p>
凭什么让改 bug 占据了你大部分的时间/p>
但是,这些 “小技巧” 在企业中是行不通的,企业级项目必须为企业带来实际的价值,容不得半点马虎和欺骗。
我第一次进入企业实习时,还保留着自己在学校开发项目的狼性 ??,只要能够完成基本功能就行,保证以最快的速度完成开发。
有一天,当我洋洋得意准备早点下班时,测试同学走过来跟我说。
“喂,你的程序有 bug,这里用户下单怎么金额是负的
这件事之后,我意识到,在企业中开发项目,不能只追求开发时的效率,还要注重项目的稳定性,否则带来的额外返工时间远比开发时节省的时间要长,而且会影响同事对你的看法。如果将开发时产生的 bug 遗留到线上,后果更是不堪设想!
后来,在字节跳动和腾讯这两家大公司工作后,我进一步认识到了项目稳定性有多重要,并且积累了更多只有在大公司才能学到的提升项目稳定性的经验。
我总结了 10 个开发中通常不会考虑到的风险点,以及 16 个减少风险、提升项目稳定性的方法,分享给大家~
软件开发正是如此,表面上机器是 “死” 的,只会按照人输入的指令或编好的程序来执行,一成不变,听话的很。好像我们写好代码扔到机器上后,就可以高枕无忧。
但真的是这样么strong>我们真的可以信任机器和程序么/strong>
其实,在程序世界中危机四伏,人为因素、环境因素等可能都会对我们的程序产生影响。因此,我们必须时刻坚守软件开发的不信任原则,保持 (过于悲观),把和程序有关的一切请求、服务、接口、返回值、机器、框架、中间件等等都当做不可信的,步步为营、处处设防。
我是一个身价过亿的大项目,每天服务着上千万的用户,帮助他们获得知识与快乐。
我的小伙伴们只看到我身上的光环和荣耀,但是他们看不到我背负的压力和风险,今天终于有机会和大家倾诉我的苦衷了。
记得很多年前,我还是个孩子,只有几个小主人开发我,那段时间,我成长的很快。虽然只有几十个人使用我,但我感到非常轻松和快乐,偶尔偷会儿懒,也不会被人发现。
后来,我的功能越来越多,越来越强大。每天有数之不尽的新面孔来和我打招呼,并享受我提供的服务。渐渐地,更多开发者在我身上留下了印记,我感觉自己正在变得复杂,也开始感受到了压力。我再也找不到机会偷懒,因为我一旦休息,就会让我的主人们损失一比不小的财富。
如今,我已经是一个成熟的大项目了,每天有上千万的用户依赖我,我终于拥有了更大的价值,却也增加了很多烦恼,感受到了更大的危险。
然而最让我感到恐惧的,是那些坏家伙们!
他们和正常的用户不同,有的不断制造请求,试图将我击垮。有的绕到我的背后,试图直接控制我。有的对我虎视眈眈,监视并记录我的一举一动。还有的尝试各种非法操作,想从我身上牟取暴利。
作为一个大项目真是太累了,我不知道我还能坚持多久。
真的可信么/h3>
“地位越高,看似越安全,实则越危险。”
如今是一个软件开源和共享的时代,我们在开发项目时,或多或少会使用到 上现有的资源,比如依赖包、工具、组件、框架、接口、现成的云服务等等,这些资源能够大大提升我们的开发效率。
就拿云服务来说,几乎已经成了我们开发必备的资源,以前我们想要做一个 站,可能需要自己买一台物理服务器,然后连通 络,再把项目部署上去。而如今,直接登录大公司的云官 (像腾讯云、阿里云),然后租一台云服务器就行了,非常省事。
1. 开发工具可信么/h4>
我们通常是在大而全的开发工具中编写代码,比如 或者 。很多刚开始写代码的同学、甚至是一些经验丰富的老手,都对开发工具保持绝对的信任。
但是,由于内存不足等种种原因,开发工具其实也会抽风。
又或是项目无法运行,怎么排查都觉得没问题,这时不妨重新启动下开发工具,或者清理一下缓存,说不定项目就能正常运行了!
我记得自己有一次使用知名的开源服务器 ,就遇到了 bug,每次接受到特定的请求都会 错。刚开始我根本没有怀疑是 的问题,而是绞尽脑汁地想自己的代码哪里写错了。后来经过反复的排查和搜索,终于确认了就是 本身的 bug!
虽然开源项目并不完全可信,但是相对于私有项目而言,所有对项目感兴趣的同学可以共同发现项目中的问题,并加以解决,在一定程度上还是能够提高项目的可靠性的。
3. 依赖库可信么/h4>
我们在开发项目时,通常会用到大量的依赖库。直接在官方依赖源(比如 和 )搜索依赖库,然后使用包管理器,用一行命令或者编写配置文件就能够让其自动安装依赖,非常方便。
但是,这些发布到官方源的依赖库,就可信么/strong>
且不说基本每个开发者都有机会发布依赖库到官方,就算是互联 大公司的依赖库,也未必可信。
给我印象最深刻的就是阿里巴巴的 序列化类库 ,几乎无人不知、无人不晓,因为其极快的解析速度广受好评。但是,这个库被多次曝光存在高危漏洞,可以让攻击者远程执行命令!一般的开发者根本不会发现这点,从而给项目带来了极大的危害。
但是,对于大多数开发者来说,我相信即使在程序中偶然触发了编程语言本身的 bug,也没有足够的自信去质疑,而是直接修改代码来绕过。
确实,质疑编程语言需要一定的基础和知识储备,但是一旦发现了程序中莫名其妙的问题,建议大家不要直接忽略,可以花一些时间去探索研究,说不定你就成功地发现了一个重大的 bug,也能够加深对这门编程语言的理解。
5. 服务器可信么/h4>
服务器是项目赖以生存的宿主,服务器的性能和稳定性将直接影响到项目进程。
无论是个人开发者还是企业,通常都会直接租用大公司提供的云服务器来部署项目,省去了自己搭建和维护的麻烦。
但是大公司的云服务器就可信么/p>
不完全可信!即使现在的云服务器提供商都承诺自己的服务 SLA(服务级别协议)可以达到 5 个 9(99.999% 一年约宕机 5 分钟),甚至 6 个 9(99.9999% 一年约宕机 30 秒),但是仍然存在一定的风险。
有一个非常有名的案例,在 2013 年,中国最大的 交通讯软件出现大规模的故障,多达几亿用户受到影响。原因竟然是,市政道路建设的一个不注意,把 络光缆挖断了,就导致该软件所在服务器的无法访问。
除了可用性的不可信之外,可能还有一些安全隐私方面的问题。当然云服务商通常是不会获取用户的数据的,但也没有办法绝对相信他们。毕竟数据的隐私对企业至关重要,这也是为什么大的公司都会搭建属于自己的服务器机房和 络。
因此,不要过于信任数据库,应当使用缓存之类的技术帮助数据库分担压力,并定期备份。否则一旦数据库宕机或数据丢失,带来的损失是不可估量的!
7. 缓存服务可信么/h4>
缓存是开发高性能程序必备的技术,通过将数据库等查询较慢的数据存放在内存中,直接从内存中读取数据,以提升查询性能。有了缓存之后,项目不仅能够支持更多人同时查询数据,还能够保护数据库。
目前比较主流的缓存技术有 、 等,可以自己在服务器搭建,也可以直接租用大公司提供的云缓存服务。
8. 对象存储可信么/h4>
项目中,经常会有用户上传图片或文件的功能,这类数据通常较大,用数据库存储不太方便。虽然我们可以将文件直接存到服务器上,但更好的做法是使用专门的对象存储服务。
可以简单地把对象存储当做一个大的文件夹,我们可以通过它直接上传和下载文件。大的云服务商也都提供了专业的对象存储服务,而无需自己搭建,那么对象存储可信么/p>
一般情况下,上传到对象存储的文件是不会缺失或丢失的,而且还可以将已上传的数据进行跨园区同步,起到备份的作用。
因此,不能完全相信对象存储,虽然大部分情况下大公司的对象存储服务很可靠,但不能确保万无一失。尤其是同步备份的场景下,是否真的同步成功了,又有多少同学关心过呢妨写个程序去验证和保障。
9. API 接口可信么/h4>
在开发中,我们经常会调用其他系统提供的 API 接口来轻松实现某种功能。比如查询某地的天气,可以直接调用其他人提供的天气查询接口,而无需自己编写。我们也可以提供 API 接口给其他人使用,尤其是在微服务架构中,各服务之间都是以接口调用的形式实现交互协作的。
几乎所有的 API 接口提供者都会说自己的接口有多安全、请放心使用,那么 API 接口真的可信么/p>
其实,API 接口是最不可信的资源!
首先,API 接口的提供方可以是任何开发者,很难通过他们的一面之词来确定接口的稳定性和安全性。
即使这个接口性能很高、也很安全,但是你并不了解有多少人和你在同时使用这个接口,也许只有你,又也许是 100 万个其他的开发者呢这个竞争条件下,接口的 (query per second 每秒查询数)还能达到预期么口返回时长真的不会超时么/p>
更有甚者,偷偷地把 API 接口改动了,却没有给调用者发送通知,这样接口的调用方全部都会调用失败,严重影响项目的运行!
因此,我们在调用第三方 API 接口时,一定要慎重、慎重、再慎重!
此外,如果我们是 API 接口的提供者,也要注意保护好自己的 API 接口,避免同时被太多的开发者调用,导致接口挂掉。
听起来非常爽,那 Serverless可信么/p>
使用 Serverless,虽然能够大大提升开发和运维效率,但是其相对服务器等资源而言,更不可信!
首先,Serverless 本身就是部署在服务器上的,难免会受到服务器的影响。
其次,Serverless 服务不会长期保持应用的状态,而是随着请求的到来而启动,存在冷启动时期,虽然也有很多相关的优化和解决方案,但仍无法精确地保证接口的性能,尤其是在高并发场景下,性能往往达不到预期。
最重要的是,当你选择使用 Serverless 服务时,你就和某云服务提供商绑定了,后续想要迁移是非常困难的!试想一下,你项目的所有功能都交给别人来维护,真的是好事么旦云服务提供商改造了架构或接口,你的代码也要随之改动,而这种改动却不是由自己控制的!
当然,Serverless 具有非常多的优点,也是云计算技术发展的必然趋势,只是希望大家在使用前,考虑到那些可能的风险,并做好应对措施。
1. 编程习惯
要减少程序中的风险,首先要养成良好的编程习惯。
首先,在写代码时,一定要保持良好的心态,不要仓促或者以完成任务的心态去写代码。如果仅仅是为了完成需求,那么很有可能不会注意到代码中的风险,甚至是发现了风险也懒得去修补,这样确实能够节约开发的时间,但是后面出现问题后,你还是要花费更多的时间去排查、沟通和修复 bug。拔苗助长,适得其反。
在写代码时,如果在一个地方多次使用相同且复杂的变量名或字符串,建议不要手动去敲,而是用大家最喜欢的 “复制粘贴”,防止因为手误而导致的 bug。
2. 异常处理
程序的运行风云变幻,同一段代码在不同情况下也可能会产生不同的结果,甚至是异常。因此很多主流的编程语言中都有异常处理机制,比如在 中,先用 捕获异常、再用 处理异常、最后用 释放资源和善后。
在编程时,要合理利用异常处理机制,来防御代码中可能出现的种种问题。通常在异常处理中,我们会记录错误日志、执行错误上 和告警、重试等。
比如不信任数据库,那就在查询和操作数据时添加异常处理,一旦数据库抽风导致操作失败,就在日志中记录失败信息,并通过邮件、短信等告警方式通知到开发者,就能第一时间发现问题并排查。必要时还可以实现自动重试,省去一部分人工操作。
4. 流量控制
上面提到,所有的请求都是不可信的,不仅仅是请求的值,还有请求的量和频率。对于所有接口,都要限制它的调用频率,防止接口被大量瞬时的请求刷爆。对于付费接口,还要防止用户对接口的请求数超过原购买数。
此外,还有一种容易被忽视的情况,假如你的接口 A 中又调用了其他人的接口 B,也许你的接口 A 自身的逻辑能够承受每秒 1000 个请求,但是你确定接口 B 可以承受么/p>
因此,需要进行流量控制,不仅仅是预防接口被刷爆,还可以保护内部的服务和调用。
什么,你说你的接口很牛逼,每秒能抗 100 万个请求,也没有调用其他的服务,那我就找 100 万 + 1 个人同时请求你的接口,看你怕不怕!
5. 回滚
有时,我们对项目的操作可能是错误的,可能是人工操作,也可能是机器操作,从而导致了一些线上故障。这时,可以选择回滚。
回滚是指撤销某个操作,将项目还原到之前的状态,这里介绍几种常见的回滚操作。
数据回滚
有时,我们想要批量插入数据,但是数据插入到一半时,程序突然出现异常,这个时候我们就需要把之前插入成功的数据进行回滚,就好像什么都没发生过一样。否则可能存在数据不一致的风险。
最常见的方式就是使用事务来处理数据库的批量操作,当出现异常时,执行数据库客户端的回滚方法即可。
配置回滚
如果将项目的配置信息,比如数据库链接地址,写死到代码中,一旦配置错了或者地址发生变更,就要重新修改代码,非常麻烦。
比较好的方式是将配置发布到配置中心进行管理,让项目去动态读取配置中心的配置。如果不小心发布了错误的配置,可以直接在配置中心进行回滚,将配置还原。
发布回滚
没有人能保证自己的代码正确无误,很多时候,项目在测试环境验证时没有发现任何问题,但是一上线,就漏洞百出。这就说明我们最新发布的代码是存在问题的。
这时,最简单的做法就是进行版本回滚,将之前能够正常运行的代码重新打包发布。大公司一般都有自己的项目发布平台,能够使用界面一键回滚,自动发布以前版本的项目包。
但是一切都不可信,集群也有可能挂掉!
那么可以用第二种方案,一级缓存挂掉,我们就再搞一个二级缓存顶上!
通常,在高并发项目中,我们会设计多级缓存,即分布式缓存 + 本地缓存。当请求需要获取数据时,先从分布式缓存(比如 ) 中查询,如果分布式缓存集体宕机,那就从本地缓存中获取数据。这样,即使缓存挂掉,也能够帮助系统支撑一段时间。
这里可能和一些多级缓存的设计不同,有时,我们会把本地缓存作为一级缓存,缓存一些热点数据,本地缓存找不到值时,才去访问分布式缓存。这种设计主要解决的问题是,减少对分布式缓存的请求量,并进一步提升性能,和上面的设计目的不同。
我们的项目其实远比想象的要脆弱,很多服务经常因为各种原因出现问题。比如搞活动时,大量用户同时访问会导致对项目服务的请求增多,如果项目顶不住压力,就会挂掉。
为了防止这种风险,我们可以采用服务降级策略,如果系统实在无法为所有用户提供服务,那就退而求其次,给用户直接返回一个 “友好的” 提示或界面,而不是强行让项目顶着压力过劳死。
配合服务熔断技术,可以根据系统的负载等指标来动态开启或关闭降级。比如机器的 CPU 被占用爆满时,就开启降级,直接返回错误;当机器 CPU 恢复正常时,再正常返回数据、执行操作。
就是比较有名的微服务熔断降级框架。
9. 数据补偿
当检测出数据不一致后,我们就要进行数据补偿,比如将没有同步的数据再次进行同步、将不一致的数据进行更新等。
除了用来解决主动检测出的数据不一致,数据补偿也被广泛用于业务设计和架构设计中。
比如调用某个接口查询数据失败后,停顿一段时间,然后自动重试,或者从其他地方获取数据。又如消息队列的生产者发送消息失败时,应该自动进行补发和记录,而不是直接把这条消息作废。
数据补偿的思想本质上是保证数据的最终一致性,数据出错不可怕,知错能改就是好孩子。这种思想也被广泛应用于分布式事务等场景中。
11. 心跳机制
接口可是个复杂多变的家伙,如果我们的项目依赖其他的接口来完成功能,那么最好保证该接口一直活着,否则可能会影响项目的运行。
举个例子,我们在使用银行卡支付时,肯定需要调用银行提供的接口来获取银行卡的余额信息,如果这个接口挂了,获取不到余额,用户也就无法支付,也就损失了一笔收入!
因此,我们需要时刻和重要的接口保持联系,防止他们不小心死了。可以采用心跳机制,定时调用该接口或者发送一个心跳包,来判断该接口是否仍然存活。一旦调用超时或者失败,可以立刻进行排查和处理,从而大大减少了事故的影响时长。
13. 弹性扩缩容
梦想还是要有的,说不定突然,我们原先只有 100 人使用的小项目突然就火了,有几十万新用户要来使用。
但是,由于我们的项目只部署在一台服务器上,根本无法支撑那么多人,直接挂掉,导致这些用户非常扫兴,再也不想用我们的项目了。
15. 监控告警
项目的运行不可能一直正常,但是我们不可能 24 小时盯着电脑屏幕来监视项目的运行情况吧不能完全不管项目,出了 bug 等着用户来投诉。
因此,最好的方式是给业务添加监控告警,当程序出现异常时,信息会上 到监控平台,并第一时间给开发者发送通知。还可以通过监控平台实时查看项目的运行情况,出了问题也能更快地定位。
看到这里,肯定有同学会吐槽,怎么写个程序要考虑那么多和功能无关的问题。本来五分钟就能写完的代码,现在可能一个小时都写不完!
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!