架构师训练营 - 学习笔记 - 第十一周
这周智慧老师生病了,周六的课顺延到下周,祝老师早日康复🌹。。。
思考与感悟
小技巧
阅读开源代码,把里面对你有用的关键代码移植到你的项目中,而不是直接引用开源组件(太厚重),这样可以使你的工作量更加看起来突出、饱满、更有技术含量,在老板、同事面前可以装X, 你懂的。。。
例如,开源的 Web 应用防火墙 ModSecurity,把里面的正则表达式(久经考验的宝藏)拿到你的项目中来,这样就可以快速拥有检测恶意攻击的能力,而且可以让你的代码看起来很高大上。因为这些正则表达式已经经过十几、几十年的实践检验了——站在巨人的肩膀上。
TIPS
架构师要考虑三个维度:技术、业务、团队。
架构师不写文档,就等于程序员不写代码。
训练营课程主要包含:架构方法、程序设计、架构设计。
工作经验主要体现在你做设计的时候如何去取舍,怎么去把握平衡,软件设计是一场 trade-off 的选择.
大数据、机器学习等这几年已经成为常态,学习它们只能让你维持现状、不被淘汰,如果你不学习,现状都维持不了。
保持简单,使问题易于发现,并且能够快速解决,简单要比复杂难。
安全 - 2020/8/13 - 周四
组件设计原则
这次课智慧老师把上周没讲完的组件设计原则讲了下。
在没有编程语言的时候就已经有了软件组件
软件的复杂度和它的规模成指数关系
一个复杂度为 100 的软件系统,如果能拆分成两个互不相关、同等规模的子系统,那么每个子系统的复杂度应该是 25, 而不是 50。 软件开发这个行业很久之前就形成了一个共识,因该将复杂的软件系统进行拆分,拆成多个更低复杂度的子系统,子系统还可以继续拆分成更小粒度的组件。也就是说,软件需要进行模块化、组件化设计。
组件内聚原则
组件内聚原则主要讨论哪些类应该聚合在同一个组件中,以便组件既能提供相对完整的功能,又不至于太过庞大。
复用发布等同原则
共同封闭原则
共同复用原则
复用发布等同原则
复用发布等同原则是说,软件复用的最小粒度应该等同于其发布的最小粒度。也就是说,如果你希望别人以怎样的粒度复用你的软件,你就应该以怎样的粒度发布你的软件。者其实就是组件的定义了,组件是软件复用和发布的最小粒度软件单元。这个粒度既是复用的粒度,也是发布的粒度。
版本号规定:A.B.C, 主版本号.次版本号.修订号
主版本号升级,表示组件发生了不向前兼容的重大修订;
次版本号升级,表示组件进行了重要的功能修订或者 bug fix, 但是组件时向前兼容的;
表示组件进行了不重要的功能修订或者 bug fix.
共同封闭原则
共同封闭原则是说,我们应该将哪些会同时修改,并且为了相同目的而修改的类放到同一个组件中。而将不会同时修改,并且不会为了相同目的而修改的类放到不同的组件中。
组件的目的虽然时为了复用,然而开发中常常引发问题的,恰恰在于组件本身的可维护性。如果组件在自己的生命周期中必须经历各种变更,那么最好不要涉及其他组件,相关的变更都在同一个组件中。这样,当变更发生的时候,只需要重新发布这个组件就可以了,而不是一大堆组件都受到牵连。
共同复用原则
共同复用原则是说,不要强迫一个组件的用户依赖他们不需要的东西。
这个原则一方面是说,我们应该将互相依赖,共同复用的类放在一个组件中。比如说,一个数据结构容器组件,提供数组、Hash 表等各种数据结构容器,那么对数据结构遍历的类、排序的类也应该放在这个组件中,以使这个组件中的类共同对外提供服务。
另一方面,这个原则也说明,如果不是被共同依赖的类,就不应该放在同一个组件中。如果不被依赖的类发生变更,就会引起组件变更,进而引起使用组件的程序发生变更。这样就会导致组件的使用者产生不必要的困扰,甚至讨厌使用这样的组件,也造成了组件复用的困难。
组件耦合原则
组件内聚原则讨论的时组件应该包含哪些功能和类,而组件耦合原则讨论组件之间的耦合关系应该如何设计。
无循环依赖原则
稳定依赖原则
稳定抽象原则
无循环依赖原则
组件依赖关系中不应该出现环。 A -> B -> C -> A, 就形成了循环依赖。
很多时候,循环依赖实在组件变更过程中逐渐形成的,组件 A 1.0 -> B 1.0,后来组件 B 升级到1.1,升级的某个功能依赖组件 A 的 1.0 版本,于是形成了循环依赖。
如果组件设计的边界不清晰,组件开发设计缺乏评审,开发者只关注自己开发的组件,整个项目对组件依赖管理没有统一的规则,很有可能出现循环依赖。
稳定依赖原则
稳定依赖原则说,组件依赖关系必须指向更稳定的方向。很少有变更的组件是稳定的,也就是说,经常变更的组件时不稳定的。根据稳定以来原则,不稳定的组件应该依赖稳定的组件,而不是反过来。
反过来说,如果一个组件被更多组件依赖,那么它需要相对是稳定的,因为想要变更一个被很多组件依赖的组件,本身就是一件困难的事。相对应的,如果一个组件依赖了很多的组件,那么它相对也是不稳定的,因为它依赖的任何组件变更,都可能导致自己的变更。
稳定依赖原则通俗地说就是,组件不应该依赖一个比自己还不稳定的组件。
稳定抽象原则
一个组件的抽象化程度应该与其稳定性程度一致。也就是说,一个稳定的组件应该是抽象的,而不稳定的组件应该是具体的。
这个原则对具体开发的指导意义就是:如果你设计的组件时具体的、不稳定的,那么可以为这个组件对外提供服务的类设计一组接口,并把这组接口封装在一个专门的组件中,那么这个组件就相对比较抽象、稳定。
Java 中JDBC 就时这样的一个例子,应用程序开发只需要调 JDBC 的接口编程就可以了。而发布应用的时候,我们指定具体的实现组件,可以是 MySQL 实现 JDBC 组件,也可以时 Oracle 实现的 JDBC 组件。
组建的边界与依赖关系,不仅仅是技术问题
组件的边界与依赖关系划分,不仅需要考虑技术问题,也要考虑业务场景问题。易变与稳定,依赖与被依赖,都需要放在业务场景中去考察。有的时候,甚至不只是技术和业务的问题,还需要考虑人的问题,在一个复杂的组织中,组件的依赖与设计需要考虑人的因素,如果组件的功能划分设计到部门的职责边界,甚至会和公司内的政治关联起来。
安全架构
XSS 攻击
跨站脚本攻击(Cross Site Scripting) XSS
XSS 攻击防御手段
过滤与消毒:XSS 攻击者一般都是通过在请求中嵌入恶意脚本达到攻击目的,这些脚本一般用户输入中不使用的,如果进行过滤和消毒处理,即对某些 HTML 危险字符转义,如 “>” 转义为 ">", "<" 转义为 "<" 等,就可以防止大部分攻击。为了避免对不必要的内容错误转义,如 "3<5" 中的 "<", 需要进行文本匹配后再转义,如 “<img scr=” 这样的上下文中 “<”才转义。事实上,消毒几乎是说有网站最必备的 XSS 防攻击手段。
SQL 注入攻击
获取数据库表结构信息的手段
开源:如果网站采用开源软件搭建,如用 Discuz! 搭建论坛网站,那么网站数据库结构就是公开的,攻击者可以直接获得。
错误回显:如果网站开启错误回显,攻击者故意构造非法参数,服务端异常信息会输出到浏览器端,为攻击猜测数据库表结构提供了便利。
盲注:网站关闭错误回显,攻击者根据页面变化情况判断 SQL 语句的执行情况,据此猜测数据库表结构,此种方式攻击难度较大。
注入攻击防御手段
消毒:和防 XSS 攻击一样,请求参数消毒是一种比较简单粗暴又有效的手段。通过正则匹配,过滤请求数据中可能注入的 SQL.
如:“drop table“、”\b(?:update\b.*?\bset|delete\b\W*?\bfrom)\b" 等。
SQL 预编译参数绑定:使用预编译手段,绑定参数是最好的防 SQL 注入方法。目前许多数据访问层框架,如 myBatis, Hibernate 等,都实现 SQL 预编译和参数绑定,攻击者的恶意 SQL 会被当做 SQL 的参数,而不是 SQL 命令被执行。
CSRF 攻击
跨站请求伪造(Cross-Site Request Forgery)
CSRF 攻击防御手段
表单 Token: CSRF 是一个伪造用户请求的操作,所以需要构造用户请求的所有参数才可以。表单 Token 就是阻止攻击者获得所有请求参数的可能,在页面表单中增加一个随机数 Token, 每次请求的 Token 都不相同,请求提交后检查 Token 的值是否正确以确定请求提交者是否合法。
验证码:相对来说,验证码则更加简单有效,即请求提交时,需要用户输入验证码,以避免在用户不知情的情况下被攻击者伪造请求。但是输入验证码时一个糟糕的用户体验,所以必要的时候才使用,如支付交易等关键页面。
Referer check: HTTP 请求头的 referer 域中记录着请求来源,可通过检查请求来源,验证其是否合法。但是该方法又一定的局限性,referer 也并不一定总能得到。
其他需要关注的攻击和漏洞
Error Code: 也称作错误回显,许多 Web 服务器默认是打开一场信息输出的,即服务器端未处理的异常堆栈信息也会直接输出到客户端浏览器,这种方式虽然对程序调试和错误报告有好处,但同时也给黑客造成可乘之机。通过故意制造非法输入,使系统运行时出错,获得异常信息,从而寻找系统漏洞进行攻击。防御手段也很简单,通过配置 Web 服务器参数,跳转500页面(HTTP 响应码 500 表示服务器内部错误)到专门的错误页面即可,这个功能 Web 应用常用的 MVC 框架也可以做到。
HTML 注释:为了程序调试方便或其他不恰当的原因,有的时候程序开发人员会在 PHP, JSP 等服务器页面程序中使用 HTML 注释语法进行程序注释,这些 HTML 注释就会显示在客户端浏览器,给黑客造成攻击便利。程序最终发布前需要进行代码 review 或自动扫描,避免 HTML 注释漏洞。
文件上传:一般网站都会有文件上传功能,设置头像、分享视频、上传附件等。如果上传的时可执行的程序,并通过该程序获得服务器端命令执行能力,那么攻击者几乎可以在服务器上为所欲为,并以此为跳板攻击集群环境的其他机器。最有效的防御手段是设置上传文件白名单,只允许上传可靠的文件类型。此外还可以修改文件名、使用专门的存储等手段,保护服务器免受上传文件攻击。
路径遍历:攻击者在请求的 URL 中使用相对路径,遍历系统未开放的目录和文件。防御方法主要是讲JavaScript, CSS 等资源文件独立服务器、独立域名,其他文件不使用静态 URL 访问,动态参数不包含文件路径信息。
Web 应用防火墙
开源 Web 应用防火墙 ModSecurity
ModSecurity 是一个开源的 Web 应用防火墙,探测攻击并保护 Web 应用程序,既可以嵌入到 Web 应用服务器中,也可以作为一个独立的应用程序启动。ModSecurity 最早只是 Apache 的一个模块,现在已经有 Java, .NET 多个版本,并支持 Nginx.
ModSecurity 采用处理逻辑与规则集合分离的架构模式。处理逻辑负责请求和响应的拦截过滤,规则加载执行等功能。而规则集合则负责对具体的攻击的规则定义、模式识别、防御策略等功能。处理逻辑比较稳定,规则集合需要不断针对漏洞进行升级,这是一种可扩展的架构设计。
网站安全漏洞扫描
和电脑安全漏洞扫描一样,网站也需要安全漏洞扫描。
网站安全漏洞扫描工具是根据内置规则,模拟黑客攻击行为,用以发现网站安全漏洞的工具。许多大型网站的安全团队都有自己开发的漏洞扫描工具,不定期的对网站的服务器进行扫描,查漏补缺。
目前市场上也有很多商用的网站安全扣动扫描平台。
信息加密技术及密钥安全管理
密码不能明文保存。
通常,为了保护网站的敏感数据,应用需要对这些信息进行加密处理,信息加密技术可分为三类:
单项散列加密,对称加密,非对称加密。
单向散列加密
对称加密
非对称加密
密钥安全管理与加解密服务系统架构
密钥被切成多分存储。
密钥服务拼接密钥,使用加密算法进行加密。
应用服务器调用加密接口。
反垃圾邮件
贝叶斯分类算法
一号箱子放有红色球和白色球各20个,二号箱子放有白色球10个,红色球30个,现在随机挑选一个箱子,取出来一个球的颜色是红色的,请问这个球来自一号箱子的概率是多少?
布隆过滤器黑名单
电子商务风险控制
由于买卖双方的信息不对等,交易本来就存在风险,而当交易在网上发生的时候,买卖双方彼此一无所知,交易风险也就更加难以控制。可以说,交易安全是电子商务网站的底线。
电子商务具有多种形式,B2B,B2C,C2C 每种交易的场景都不相同,风险如下:
账户风险:包括账户被黑客盗用,恶意注册账号等几种情形。
买家风险:买家恶意下单占用库存进行不正当竞争;黄牛利用促销抢购低价商品;此外还有良品拒收,欺诈退款以及常见于B2B交易的虚假询盘等。
卖家风险:不良卖家进行恶意欺诈的行为,例如货不对板,虚假发货,炒作信用等,此外还有发布违禁商品、侵权产品等。
交易风险:信用卡盗刷,支付欺诈,洗钱套现等。
大型电商网站都配备有专门的风控团队进行风险控制,风控的手段也包括自动和人工两种方式。机器自动识别为高风险的交易和信息会发送给风控审核人员进行人工审核,机器自动风控的技术和方法也不断通过人工发现的新风险类型进行逐步完善。
机器自动风控的技术手段主要有规则引擎和机器学习
规则引擎
当交易的某些指标满足一定条件的时候,就会被认为具有高风险的欺诈可能性。比如:
用户来自欺诈高发地区;
交易金额超过某个数值;
和上次登录的地址距离差距很大;
用户登录地与收货地不符;
用户第一次交易;
...
大型网站在运营过程中,结合业界的最新发现,会总结出数以千计的此类高风险交易规则。一种方案是在业务逻辑中通过编程方式使用 if...else... 代码实现这些规则,可以想见,这些代码会非常庞大,而且由于运营过程中不断发现新的交易等闲类型,需要不断调整规则,代码也需要不断修改...
机器学习
规则引擎虽然技术简单,但是随着规则的逐渐增加,出现规则冲突,难以维护等情况,而且规则越多,性能也越差。大型互联网应用更倾向于使用机器学习模型进行风控。
高可用 - 2020/8/22 - 周六
高可用系统的度量
可用性指标
业界通常用多少个9来衡量网站的可用性,如 QQ 的可用性是4个9,即 QQ 服务99.99% 可用,这意味着 QQ 服务要保证其在所有运行时间中,只有 0.01% 的时间不可用,也就是一年中大约53分钟不可用。
网站年度可用性指标 = (1 - 网站不可用时间/年度总时间) x 100%
网站不可用时间(故障时间) = 故障修复时间点 - 故障发现(报告)时间点
对可用性的定性描述,两个9是基本可用,年度停机时间小于88小时;3个9较高可用,年度停机时间小于9小时;4个9是具有自动恢复能力的高可用,年度停机时间小于53分钟;5个9是极高可用性,年度停机时间小于5分钟。由于可用性影响因素很多,对于网站整体而言,达到4个9,乃至5个9的可用性,除了过硬的技术、大量的设备资金投入和工程师的责任心,还要有个好运气。
故障分类管理
故障处理流程及考核
引起故障的原因
硬件故障
软件 bug
系统发布
并发压力
网络攻击
外部灾害
高可用系统的架构
解耦
高内聚、低耦合的组件设计原则
面向对象基本设计原则
面向对象设计模式
领域驱动设计建模
隔离
业务与子系统隔离
微服务与中台架构
生产者消费者隔离
虚拟机与容器隔离
异步
多线程编程
反应式编程
异步通信网络编程
事件驱动异步架构
备份
集群设计
数据库复制
CAP 原理
Failover(失效转移)
数据库主主失效转移
负载均衡失效转移
如何确认失效,需要转移?
设计无状态的服务
幂等
操作一次与操作多次结果一致。
服务重复调用有时候是无法避免的,必须保证服务重复调用和调用一次产生的结果相同,即服务具有幂等性。对于交易等操作,问题就会比较复杂,需要通过交易编号等信息进行服务调用有效性检验,只有有效的操作才继续执行。
事务补偿
传统事务的 ACID
原子性、一致性、隔离性、持久性
分布式事务的 BASE
基本可用、软状态、最终一致性
事务补偿:通过执行业务逻辑逆操作,是事务回滚到事务前状态
重试
远程服务可能会由于线程阻塞、垃圾回收或者网络抖动,而无法即时返回响应,调用者可以通过重试的方式修复单词调用的故障。
上游调用者超时时间要大于下游调用者超时时间之和。
熔断
当某个服务出现故障,响应延迟或者失败率增加,继续调用这个服务会导致调用者请求阻塞,资源消耗增加,进而出现服务级联失效,这种情况下使用断路器阻断对故障服务的调用。
断路器三种状态:关闭,打开,半开
Spring Cloud 断路器实现:Hystrix
限流
高并发场景下,如果系统的访问量超过了系统的承受能力,可以通过限流对系统进行保护。
如果访问量超过了系统的最大处理能力,那就丢弃一部分用户的请求,保证整个系统可用,保证大部分用户是可以访问系统的。好过整个系统崩溃。
限流的几种算法:
计数器算法(固定窗口,滑动窗口)
令牌桶算法
漏桶算法
计数器算法(固定窗口)算法
计数器算法(滑动窗口)算法
令牌桶算法
漏桶算法
自适应限流
降级
关闭非核心功能,例如,确认收货,商品评论
异地多活
数据中心所在城市遭遇了地震、火灾、停电等。
将数据中心分布在不同地点的机房里,这些机房都可以对外提供服务,即使某一个机房不可用,系统也不会宕机,依然保持可用。
异地多活难点是数据一致。
高可用系统的运维
发布
应用的不断发布,用户需要面对的是每周一到两次的宕机故障。
自动化测试
使用自动化测试来进行回归测试,确认已有的功能没有被破坏。
目前大部分网站都采用 Web 自动化测试技术。
手工测试和自动化测试的总体成本
手工测试:初期成本小,但是增长很快。每次测试成本都比前一次发布的测试成本更高。
自动化测试:前期投资大,后续投入相对便宜。测试已经存在的功能几乎不需要花费任何成本。
随着事件的推移,测试会变得越来越高效,每一次发布测试,已测试的代码和待测试的代码之比都在增加。最终,会达到一个平衡点,然后自动化测试的总体成本会低于手工测试的成本。
自动化部署
持续部署三步走
持续集成
允许工程师随时向公共分支提交代码,并 *立即* 进行自动化测试。
持续交付
除了跑单元测试即软件打包,持续交付机制会将软件部署到各种测试环境中
持续部署
代码在没有人工干预的情况下被测试、构建、部署并推送到生产环境。
持续部署流程
预发布验证
即使经过严格的测试,软件部署到线上服务器之后还是会经常出现各种问题,主要原因还是测试环境和线上环境并不相同,特别是应用需要依赖的其他服务,如数据库,缓存、公共业务服务等。这些问题都有可能导致应用故障。
因此在网站发布的时候,先发布到预发布机器上,确认没问题再正式发布。
预发布的机器没有配置再负载均衡服务器上,外部用户无法访问。
代码版本控制
Single Source Of Truth.
如何进行代码管理,既能保证代码发布版本的稳定正确,同时又能保证不同团队的开发互不影响。
主干开发、分支发布
主干代码反映目前整个应用的状态,一目了然,便于管理和控制,也利于持续集成。
分支开发、主干发布
主干上的代码永远是最新发布的版本
各个分支独立进行,互不干扰
自动化发布
多个代码分支合并会主干可能会发生冲突(conflict)
灰度发布
将集群服务器分成若干部分,每天只发布一部分服务器,观察运行稳定没有故障,第二天继续发布一部分服务器,持续几天的事件才把整个集群全部发布完毕,期间如果发现问题,就只需要回滚已发布的一部分服务器即可。
网站运行监控
“不允许没有监控的系统上线”
网站运行监控对于网站运维和架构设计优化至关重要,没有监控的网站,犹如盲人骑瞎马,夜半临深渊而不知。生死未卜,提高可用性、减少故障率就更无从做起了。
监控数据采集
广义上的网站监控涵盖所有非直接业务行为的数据采集与管理,包括供数据分析师和产品设计师使用的网站用户行为日志,业务运行数据和系统性能数据等。
用户行为日志收集
用户再浏览器上所作的所有操作,页面访问路径,页面停留时长等,这些数据对统计网站 PV/UV 指标,分析用户行为,优化网站设计,个性化营销与推荐等非常重要。
用户行为日志收集手段有两种
服务器端日志收集
客户端浏览器日志收集
缺点是比较麻烦,需要再页面嵌入特定的 JS 脚本来完成。
服务器性能监控
收集服务器性能指标,如系统 Load,内存占用,磁盘IO,网络IO 等对尽早做出故障预警,及时判断应用状况,防患于未然,将故障扼杀在萌芽时期非常重要。此外根据性能监控数据,运维工程师可以合理安排服务器集群规模,架构师及时改善系统性能及调整系统伸缩性策略。
目前网站 使用比较广泛的开源性能监控工具是 Ganglia, 支持大规模服务器集群,并支持以图形的方式在浏览器展示实时性能曲线。
业务运行数据报告
网站还需要监控一些具体业务场景相关的技术和业务指标,比如缓冲命中率、平均响应延迟时间、每分钟发送邮件数目、待处理的任务总数等。不同于服务器性能监控,网站运维人员可以在初始化系统的时候统一部署,业务运行数据需要在具体程序中采集并报告,汇总后统一显示。
监控管理
监控数据采集后,除了用作系统性能评估、集群规模伸缩性预测等,还可以根据实时监控数据进行风险预警,并对服务器进行失效转移,自动负载调整,最大化利用集群所有机器的资源。
报警
服务器运行正常的情况下,其各项监控指标基本稳定在一个特定水平,如果这些指标超过某个阀值,就意味着系统可能将要出现故障,这时候就需要对相关人员报警,及时采取措施,在故障还未真正发生就将其扼杀在萌芽状态。
监控管理系统可以配置报警阀值和值守人员的联系方式,报警方式除了邮件,即时通讯工具,还可以配置收集短信,语音报警,保证发生报警时,工程师即使在千里之外、夜里睡觉也能及时通知,迅速响应。
自动控制
自动失效转移:除了应用程序访问失败时进行失效转移,监控系统也可以在发现故障的情况下主动通知应用,进行失效转移。
自动扩容:如果因访问压力大而导致服务性能指标下降,监控系统自动触发服务器集群扩容。
自动限流:根据监控指标,自动控制访问流量。
监控系统架构
高可用的价值观
保持简单,是问题易于发现,快速解决。
目标明确,解决特定环境下的具体问题。
价值回归,成本收益要合理。
故障案例分析
Log 塞满磁盘空间
线上环境 log level 不要配置为 debug, 否则有可能会塞满磁盘空间。
应用程序自己的日志输出配置和第三方组件日志输出要分别配置。
数据库 Load 居高不下
首页不用改访问数据库,首页需要的数据可以从缓存服务区或者搜索引擎服务器获取。
首页最好是静态的。
服务器不定时超时响应报警
单例对象(Singleton object)中多个方法使用了 synchronized 修饰符,由于 this 对象只有一个,即使执行不同方法,所有的并发请求也都要排队获得这唯一的一把锁。一般情况下,都是一些简单的操作,获得锁,迅速完成操作,释放锁,不会引起线程排队。但是某个需要执行远程调用的方法也被加了 synchronized, 这个方法只是偶尔会被执行,但是每次执行都需要较长的时间才能完成,这段时间锁被占用,所有的线程都要等待,响应超时,这个方法执行完后释放锁,其他线程迅速执行,超时解除。
经验教训:
使用锁操作要谨慎,特别注意在有锁的方法中进行长时间的 IO 操作的时候。
针对使用锁的不同场景,使用不同锁对象,而不是简单的在所有方法上都加上 synchronized
没有新应用发布,无大量用户的并发请求,但是数据库服务器突然 Load 飙升,并很快失去响应
事故原因:一个缺乏经验的工程师关闭了缓存服务器集群中全部的几十台 Memorycached 服务器,导致了网站全部瘫痪的重大事故。
经验教训:当混村已经不仅仅是改善性能,而是成为网站架构不可或缺的一部分时,对缓存的管理就需要提高到和其他服务器一样的级别。
某应用发布后,服务器立即崩溃
应用程序 Web 环境使用 Apache + JBoss, 用户请求通过 Apache 转发到 JBoss.
在发布时,Apache 和 JBoss 同时启动,由于 JBoss 启动时需要加载很多应用并初始化,花费时间较长,结果 JBoss 还没有完全启动,Apache 就已经启动完毕开始接收用户请求,大量请求阻塞在 JBoss 进程中,最终导致 JBoss 崩溃。网站还有很多类似的场景,都需要后台服务准备好,前台应用才能启动,否则就会导致故障。
这种情况被戏称为:“姑娘们还没穿好衣服,老鸨就开门迎客了”。
经验教训:老鸨开门前要检查下姑娘们是否穿好了衣服。就本例来说,在 Java 应用程序中加入一个特定的动态页面(比如只返回 OK 两个字母),启动脚本先启动 JBoss, 然后在脚本中不断用 curl 命令访问这个特定页面,直到收到 OK, 才启动 Apache.
用户上传图片变得越来越慢(一两秒 -> 几十秒)
原因分析:图片需要使用存储,最有可能出错的地方是存储服务器。检查存储服务器,发现大部分文件只有几百K, 而又几个文件非常大,又数百兆,读写这些大文件一次需要几十秒,这段时间,磁盘基本被这个文件操作独占,导致其他用户的文件操作缓慢。
经验教训:
存储的使用需要根据不同文件类型和用途进行管理,图片都是小文件,应该使用专用的存储服务器,不能和大文件公用存储。批处理用的大文件可以使用其他类型的分布式文件系统。
监控发现某个时段内,某些应用突然变慢,内部网络访问延迟非常厉害。
原因分析:某个工程师在线上生产环境进行性能压力测试,占用了大部分交换机带宽。
经验教训:
访问线上生产环境要规范,不小心就会导致大事故。
某应用发布后,数据库 Load 迅速飙升,超过报警值,回滚发布后报警消除。
原因分析:该应用发布后出现大量数据库读操作,而这些数据本来应该从分布式缓存读取。检查缓存,发现数据已经被缓存了。检查代码,发现访问缓存的那行代码被注释掉了。原来工程师在开发的时候,为了测试方便,特意注释掉去读缓存的代码,结果开发完成后忘记把注释去掉,直接提交到代码库被发布到线上环境。
经验教训:
代码提交前再三检查,使用 diff 命令/Beyond Compare 等比较工具,确认没有提交不该提交的代码。
加强 code review, 代码再正式提交前必须被至少一个其他工程师做过 code review, 并且共同陈丹因代码引起的故障责任。
某应用更新某功能后,有少量用户投诉无法正常访问该功能,一点击就显示出错信息。
原因分析:分析这些用户,都是第一次使用该功能,检查代码,发现程序根据历史使用记录构造一个对象,如果该对象为 null, 就会导致 NullPointerException.
经验教训:
程序在处理一个输入的对象时,如果不能明确该对象是否为空,必须做空指针判断。
程序在调用其他方法时,输入的对象尽量保证不是 null, 必要时构造空对象(使用空对象模式)。
参考链接
https://xie.infoq.cn/article/bef674e6a8b275c1cb92f2587
版权声明: 本文为 InfoQ 作者【心在飞】的原创文章。
原文链接:【http://xie.infoq.cn/article/7cfb810fc4fbd871acd71c58c】。文章转载请联系作者。
评论 (1 条评论)