在这里,我本人只是对网上了解到的几种设计思想说说自己的经历还有想法而已,而且是相对比较简单的那种。
1、防误措施(Mistake Proofing)
我们在平时的程序设计过程中,是不是有对自己的接口做一下基本的校验呢?比如在入参的格式、尤其是对操作接口。想起以前刚出来打代码的时候,一个注册接口,代码写的及其简单,只有一个赋值操作,然后就直接怼数据库,最多就是在前端js做一下字段的简单校验,自己心里还很自豪。直到有一天,遇到一个技术大佬,他说,永远不要相信别人给你的数据,处处判断,才是最好的。大佬说的虽然有些绝对,但是还是有一定的道理。后来,我的一个简单插入接口,我会对需要校验的地方基本都校验一遍,面对需要更新的接口,比如更新状态,我会对原有的状态进行判断,并且对入参的字段进行校验。校验虽然会花费较多时间,但是可能尽可能减少用户的误操作等等。
2、边界情况(Edge Case)
这样的设计思想,在我们程序设计的时候,需要认真对待,往往bug也容易出现在这个地方,对于测试,也是经常采用这种方法设计测试用例,作为一名合格的程序员,也需要对自己的接口进行测试。记得大学时候,上软件测试的时候,老师说,设计测试用例,就有一种叫做边界测试。比如说,你在设计程序时,合法的值为从2到10的整形数字,你如果从2到10的整形数字,每一个都拿出来测,发现都没有问题,你是不是觉得自己的程序没什么问题啦。哈哈哈,那就错了,你应该重点是放在2之前,10之后,比如-1,0,1,11等等,还有10.33等等。设计了10个测试用例没有发现问题,远不如设计一个边界值一个发现问题来得有意义。
3、耐用性(Durability)
在我看来,就是数据和接口的耐用程度。简单来说,在数据上,就好比我们把数据保存进数据库,方便我们借助数据库的属性,开发各种业务。接口上,就是要让我们的接口尽可能地好用,不能报空指针等简单可解决的bug。
4、解耦(Decoupling)
当我们在做程序设计的时候,哪怕是最基础的逻辑,也应该要符合我们的OCP原则。举个例子,Spring Ioc 就是为了把对象的创建和维护从原来的由调用方负责这种强耦合的设计模式换成通过Spring容器负责,解耦的做法就是通过把内部逻辑封装起来,暴露对外统一API接口,调用方不需要了解被调用方的内部逻辑实现,只需要知道提供什么可能即可。简单来说,解耦的作用就是在与能够复用,把所有高内聚的功能独立出来,然后根据实际需求进行组装。
5、舱壁模式(Bulkhead)
在分布式系统的设计中就使用这种模式。比如现在的SpringBoot模式,在我的理解就是这个模式的一个体现。这种模式把系统的各个模块在进程跟资源上进行隔离,使得系统不会因为某个功能模块出现失败而导致全局瘫痪。
1)数据隔离
数据隔离主要是体现在数据库方面的隔离,比如库隔离、表隔离、权限隔离等。
2)资源隔离
在我们的微服务框架里面,有这么一个组件就是遵守这个模式,那就是断路器 Hystrix,它通过为每个单独的服务提供独立的线程池,从而实现资源隔离,主要是提供以下2种方式来实现的。
①ExecutionIsolationStrategy.SEMAPHORE(信号量隔离策略)
Semaphores 隔离就是利用了 java.util.concurrent.Semaphore 的功能,从信号量获取到许可才允许执行,否则不允许执行,执行完成后要释放之前获取到的许可。同样的,每一个服务依赖都有一个自己的信号量,当该信号量的许可被获取完之后,再有线程要进行依赖调用,发现已经无可用的信号量,这时候就会被拒绝调用。信号量隔离始终都是运行在 Container 线程内的。它的优势就在于节省服务器的开销。
②ExecutionIsolationStrategy.THREAD(线程隔离策略)
所谓线程隔离,实际上就是每个依赖调用都有自己的线程池来负责处理,依赖调用都运行在自己的线程池中,当同一个依赖调用使用的线程池中的Queue size(线程数量)达到设置的阀值,就会拒绝进行依赖调用。
具体用法可以通过继承 @HystrixCommand 实现线程隔离或者交易隔离。
6、优雅降级(Graceful Degradation)
服务降级跟熔断是相类似的,只是降级比熔断来得更加优雅一些。熔断是直接断掉防止异常进一步扩大而导致雪崩,但是系统的终极级目标是提供尽可能多的服务,这个就是优雅降级的设计理念。在一些异常情况或者秒杀活动的场景下,为了保值核心业务(比如商城系统的下单,支付功能)的正常使用,会放弃掉一些非核心服务(如历史订单查询),这就是所谓的服务降级。
在微服务框架中,一般会使用 Hystrix 的 @HystrixCommand 或 Feign 的 @FeignClient 对服务进行声明,然后为每个服务配置相应的 fallback 类,最终结合起来进行服务降级。
7、熔断(Derating)
熔断本质上就是一种防御性设计。比如说,在同一个微服务体系下的系统中,A服务调用B服务。系统的QPS(访问量)是千级别的,当B服务挂掉了,A服务的线程在短时间内会内存耗尽,导致暂时性假死,从而形成大量的A请求积压起来导致情况恶化,最终造成雪崩。
在SpringCloud技术体系中,熔断就是Hystrix体现的一种思想。Hystrix可以通过监控一段时间内的异常次数和响应速度来判断当前服务的健康状况,若服务健康状况不佳进行熔断,熔断之后,新的请求将不再调用失败的业务,而是通过快速失败或者优雅降级的方式来快速给用户响应。
8、冗余(Redundancy)
所谓的冗余指的通过重复配置关键组件或部件,保证在关键组件失效的情况下,还有备份组件可以运作,以此保证系统可以继续提供服务。
9、冷备(Cold Standby)
顾名思义,就是冷备份,其实也算是冗余设计的一种,只是更侧重于“冷”,意思是当系统发送宕机时,这个系统是需要手动启动用于替换下线的主实例,它是跟热备份相对的,热备份更多的是自动切换。
10、重试(Retry)
重试是在分布式系统下处理瞬态故障的一个基本手段,简单有效(要保证幂等性)。当然重试可能是危险的,因为他能够引起一个局部故障演变成一个重大系统故障,严重的话,可能导致系统假死。
比如说,我们的链路中,有一个服务故障率异常的时候,在极端的情况下,重试次数达到 5的四次方 = 625.这无疑是一场重试风暴,因此带来了额外的开销和线程占用,在其他新的请求又形成排队,这样的话就形成了类似的DDos恶性事件。
因此根据我们平时的经验,可以考虑以下情况:
①相对较好的是选用3次;
②重试的时间间隔要设置好,合理,防止只是因为网络抖动;
③尽量只在应用层做重试。
11、监控(Monitoring)
监控主要是技术指标监控,和业务指标监控。
监控实际上是为了更好的主动防御。一套完善的告警监控系统,能够快速通知开发与运维,开发侧能够完成紧急修复并能够协同运维进行快速部署。