降级设计
牺牲或者放弃一些数据,保证系统整体稳定
-
降低一致性
实际我们业务开发当中,并不是所有的系统数据都要求保证强一致性,为了保证强一致性往往我们需要付出更大的代价以及引发其他的一些问题,因此,能从业务角度避免强一致性的功能,就要避免强一致性的出现,只要保证最终一致性就可以,这样既可以保证系统稳定又可以让系统运行的更快,通常会有两种方式:一种是简化流程的一致性,另外一种是降低数据的一致性; -
简化流程-异步方式
举个简单的例子,例如交易下单,正常下单需要同步扣除库存,目前云采平台库存扣减需要走外网调用网销系统API,跨网络的数据传输极大的增加的故障率,同时对下单流程稳定性也会造成影响,数据量上来之后,系统响应也会变慢。若我们把库存扣减,通过异步方式来处理,下单流程只生成订单,异步处理若发现没有库存,可以给用户邮件提示“库存不足等友好的文案提示信息”,当然,这种方式可能是会损害用户体验。但是,在某些阶段,这种方式是可以使用并借鉴。其他业务系统,也可以举一反三,采用类似的处理方案; -
降低数据一致性
常用的做发就是使用缓存。对于缓存来说,我们可以采用多维度的缓存设计,例如常见的CDN图片缓存,静态资源缓存,页面缓存,分布式缓存,进程级别缓存,也可以按照用户 日期 等维度设计缓存,以及递进式缓存等等,当然缓存的具体设计细节以及最终落地方案后面我会分享,只是大家知道,通过 这样设计可以有效的降低数据库的访问压力,大量的请求都在前面被消化掉,真正达到数据库的请求很少,毕竟,让所有的请求都到达数据库,很容易拖垮数据库,造成全站崩溃。我们要知道数据库最大作用就是数据的持久化存储,不丢失。我们不能过度依赖数据库做过多的业务计算。 -
停止不重要的功能
此方案简单来说就是放弃一些不重要的功能,保证核心的功能。例如商品详情的用户评论,在系统无法承载更多的请求时候,可以停掉此功能,当然这样会给用户带来一定的体验问题,若确实给用户带来很大的伤害,如果可能,可以给用户一定的补偿,例如送一些优惠券或者积分当作补偿;
简化功能
即指返回部分数据。通常来说我们需要提供两个API,一个是全量数据API,一个是核心数据API,还拿云采商品详情页来说,有商品的核心数据例如规格等数据,还有商品的产地 保障等非重要数据。全量数据我们就返回所有数据,核心数据我们就只返回规格都涉及到交易的数据;当然,也可以在全量API里面当某些数据故障直接屏蔽调,dubbo本身支持类似配置:mock=fail:return+null,其他HTTP调用也可以做类似模拟;总的来说,我们在设计降级方案时候,一定要知道系统那些是必须有的功能,那些是可以暂时舍弃的功能,这些要提前设计好,并且能支持自动化或者半自动化,当出问题时候,可以快速切换;当然,这里面需要一个系统级别的开关,能够在线自动切换,简单的方案也可以在API里面用不同的参数来区分,然后让上游系统来驱动处理;
限流设计
通常为了解决 系统不会在过载情况下出现崩溃等严重问题,需要对并发的访问进行限速。当达到限流的条件例如系统负载过高等,就需要触发限流行为,总的来说如下:
- 拒绝服务
把多出来的请求直接拒绝掉。例如可以统计那个客户端的请求最多,直接从负载屏蔽掉,这种方式可以把一些不正常或者恶意的请求访问抵挡掉;
-
特殊请求处理:
在资源紧张情况下,把有限资源分配给重要用户,即优先处理优先级高的客户,而且他非特权客户,放在最后处理;例如优先保证我们签约用户的访问,也可以从另外一个维度,平时多统计活跃的用户,这样就先保障这些活跃用户的正常访问,具体实现方案,可以用使用第三方消息中间件采用不同优先级的队列来实现,; -
弹性伸缩
这需要自动化运维,对服务做自动化伸缩,例如Doker等,这需要很强大的运维系统,例如自动化的发布 部署 服务注册等系统支持
重试设计
这是很常用的一个设计思路。分布式系统下面,经常涉及到多个系统之间的调用,这样就会涉及到网路的问题,但是网络上有很多组建例如DNS服务,网卡,路由器,负载均衡,防火墙等等,这些设备都不一定绝对的稳定,只要有一个环节出问题,就会导致最终功能不可用,为此,我们就需要加入重试机制。
当然重试不是简单的API轮询调用N此就结束了。这样通常意义不大, 重试只有我们认为此功能是暂时故障,而不是永久的时候,才去重试。例如我们可以在业务里面定义不同的返回码,让调用方来确认那些情况是需要重试的。例如下游系统提示资源不足 或者 维护中上游可以重试,但是对于一些别的错误,例如参数非法,权限不足,这些就没有必要重试,因为是没有意义的。
重试的设计策略,见到那设计我们通过会写一个算法,依据请求次数sleep不同的时间来重试(我们目前有这样一个工具算法),当最终失败时候,就放弃,springboot本身也支持相应的策略;当然这样设计比较简单,也不够完善。例如一个API经过重试失败了,那么相近请求时间的API还会做类似的重试逻辑,最终很有可能还会失败,这样无形中也是浪费了请求资源,为此我们可以进一步优化,例如当某个API多次都失败情况下,设置个在一定时间暂时不可用的标识,然后新的这样新的请求过来之后,直接放弃请求;随后,实时监控此API状态,当服务真正回复时候,回置此状态。当然,此种实现方案,技术要求比较高,需要谨慎操作;
幂等性设计
简单来说,一次请求或者多次请求达到的结果是一致的。这种情况,通常需要一个全局ID来确认唯一性,例如订单号,订单中心在监测同一个订单号已经生成了,就不需要再次生成订单。这种设计很重要,因为系统之间调用经常会出现问题,甚至异步设计时候消息也会经常丢失;