【转】聊聊高并发系统之降级特技

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。之前已经有一些文章介绍过缓存和限流了。本文将详细聊聊降级。当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。本文将介绍一些笔者在实际工作中遇到的或见到过的一些降级方案供大家参考。

 

降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。

 

降级预案

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;

错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

 

降级按照是否自动化可分为:自动开关降级和人工开关降级。

降级按照功能可分为:读服务降级、写服务降级。

降级按照处于的系统层次可分为:多级降级。

 

降级的功能点主要从服务端链路考虑,即根据用户访问的服务调用链路来梳理哪里需要降级:

页面降级:在大促或者某些特殊情况下,某些页面占用了一些稀缺服务资源,在紧急情况下可以对其整个降级,以达到丢卒保帅;

页面片段降级:比如商品详情页中的商家部分因为数据错误了,此时需要对其进行降级;

页面异步请求降级:比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级;

服务功能降级:比如渲染商品详情页时需要调用一些不太重要的服务:相关分类、热销榜等,而这些服务在异常情况下直接不获取,即降级即可;

读降级:比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景;

写降级:比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。

爬虫降级:在大促活动时,可以将爬虫流量导向静态页或者返回空数据,从而保护后端稀缺资源。

 

自动开关降级

自动降级是根据系统负载、资源使用情况、SLA等指标进行降级。

超时降级

当访问的数据库/http服务/远程调用响应慢或者长时间响应慢,且该服务不是核心服务的话可以在超时后自动降级;比如商品详情页上有推荐内容/评价,但是推荐内容/评价暂时不展示对用户购物流程不会产生很大的影响;对于这种服务是可以超时降级的。如果是调用别人的远程服务,和对方定义一个服务响应最大时间,如果超时了则自动降级。

 

之前总结过一些的文章《使用httpclient必须知道的参数设置及代码写法、存在的风险》和《dbcp配置及jdbc超时设置总结》。在实际场景用一定主要配置好超时时间和超时重试次数和机制。

 

统计失败次数降级

有时候依赖一些不稳定的API,比如调用外部机票服务,当失败调用次数达到一定阀值自动降级;然后通过异步线程去探测服务是否恢复了,则取消降级。

 

故障降级

比如要调用的远程服务挂掉了(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)。

 

限流降级

当我们去秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时开发者会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)、无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)。

 

人工开关降级

在大促期间通过监控发现线上的一些服务存在问题,这个时候需要暂时将这些服务摘掉;还有有时候通过任务系统调用一些服务,但是服务依赖的数据库可能存在:网卡被打满了、挂掉了或者很多慢查询,此时需要暂停下任务系统让服务方进行处理;还有发现突然调用量太大,可能需要改变处理方式(比如同步转换为异步);此时就可以使用开关来完成降级。开关可以存放到配置文件、存放到数据库、存放到Redis/ZooKeeper;如果不是存放在本地,可以定期同步开关数据(比如1秒同步一次)。然后通过判断某个KEY的值来决定是否降级。

 

另外对于新开发的服务想上线进行灰度测试;但是不太确定该服务的逻辑是否正确,此时就需要设置开关,当新服务有问题可以通过开关切换回老服务。还有多机房服务,如果某个机房挂掉了,此时需要将一个机房的服务切到另一个机房,此时也可以通过开关完成切换。

 

还有一些是因为功能问题需要暂时屏蔽掉某些功能,比如商品规格参数数据有问题,数据问题不能用回滚解决,此时需要开关控制降级。

 

读服务降级

对于读服务降级一般采用的策略有:暂时切换读(降级到读缓存、降级到走静态化)、暂时屏蔽读(屏蔽读入口、屏蔽某个读服务)。在《应用多级缓存模式支撑海量读服务》中曾经介绍过读服务,即接入层缓存-->应用层本地缓存-->分布式缓存-->RPC服务/DB,我们会在接入层、应用层设置开关,当分布式缓存、RPC服务/DB有问题自动降级为不调用。当然这种情况适用于对读一致性要求不高的场景。

 

页面降级、页面片段降级、页面异步请求降级都是读服务降级,目的是丢卒保帅(比如因为这些服务也要使用核心资源、或者占了带宽影响到核心服务)或者因数据问题暂时屏蔽。

 

还有一种是页面静态化场景:

动态化降级为静态化:比如平时网站可以走动态化渲染商品详情页,但是到了大促来临之际可以将其切换为静态化来减少对核心资源的占用,而且可以提升性能;其他还有如列表页、首页、频道页都可以这么玩;可以通过一个程序定期的推送静态页到缓存或者生成到磁盘,出问题时直接切过去;

静态化降级为动态化:比如当使用静态化来实现商品详情页架构时,平时使用静态化来提供服务,但是因为特殊原因静态化页面有问题了,需要暂时切换回动态化来保证服务正确性。

 

以上都保证出问题了有预案,用户还是可以使用网站,不影响用户购物。

 

写服务降级

写服务在大多数场景下是不可降级的,不过可以通过一些迂回战术来解决问题。比如将同步操作转换为异步操作,或者限制写的量/比例。

比如扣减库存一般这样操作:

方案1:

1、扣减DB库存,2、扣减成功后更新Redis中的库存;

方案2:

1、扣减Redis库存,2、同步扣减DB库存,如果扣减失败则回滚Redis库存;

前两种方案非常依赖DB,假设此时DB性能跟不上则扣减库存就会遇到问题;因此我们可以想到方案3:

1、扣减Redis库存,2、正常同步扣减DB库存,性能扛不住时降级为发送一条扣减DB库存的消息,然后异步进行DB库存扣减实现最终一致即可;

这种方式发送扣减DB库存消息也可能成为瓶颈;这种情况我们可以考虑方案4:

1、扣减Redis库存,2、正常同步扣减DB库存,性能扛不住时降级为写扣减DB库存消息到本机,然后本机通过异步进行DB库存扣减来实现最终一致性。

 

也就是说正常情况可以同步扣减库存,在性能扛不住时降级为异步;另外如果是秒杀场景可以直接降级为异步,从而保护系统。还有如下单操作可以在大促时暂时降级将下单数据写入Redis,然后等峰值过去了再同步回DB,当然也有更好的解决方案,但是更复杂,不是本文的重点。

还有如用户评价,如果评价量太大,也可以把评价从同步写降级为异步写。当然也可以对评价按钮进行按比例开放(比如一些人的看不到评价操作按钮)。比如评价成功后会发一些奖励,在必要的时候降级同步到异步。

 

多级降级

缓存是离用户最近越高效;而降级是离用户越近越能对系统保护的好。因为业务的复杂性导致越到后端QPS/TPS越低。

 

页面JS降级开关:主要控制页面功能的降级,在页面中通过JS脚本部署功能降级开关,在适当时机开启/关闭开关;

接入层降级开关:主要控制请求入口的降级,请求进入后会首先进入接入层,在接入层可以配置功能降级开关,可以根据实际情况进行自动/人工降级;这个可以参考《京东商品详情页服务闭环实践》,尤其在后端应用服务出问题时,通过接入层降级从而给应用服务有足够的时间恢复服务;

应用层降级开关:主要控制业务的降级,在应用中配置相应的功能开关,根据实际业务情况进行自动/人工降级。如dubbo提供了调用端mock机制。

原文链接:[http://wely.iteye.com/blog/2353191]

时间: 2024-09-17 04:47:10

【转】聊聊高并发系统之降级特技的相关文章

聊聊高并发系统之限流特技-1

在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流. 缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹:而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开:而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀.抢购).写服务(如评论.下单).频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流. 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达

聊聊高并发系统之限流特技-2

接入层限流 接入层通常指请求流量的入口,该层的主要目的有:负载均衡.非法请求过滤.请求聚合.缓存.降级.限流.A/B测试.服务质量监控等等,可以参考笔者写的<使用Nginx+Lua(OpenResty)开发高性能Web应用>. 对于Nginx接入层限流可以使用Nginx自带了两个模块:连接数限流模块ngx_http_limit_conn_module和漏桶算法实现的请求限流模块ngx_http_limit_req_module.还可以使用OpenResty提供的Lua限流模块lua-resty

【转】聊聊高并发系统之队列术

队列在数据结构中是一种线性表,从一端插入数据,然后从另一端删除数据.本文目的不是讲解各种队列算法,而是在应用层面讲述使用队列能解决哪些场景问题.   在我开发过的系统中,不是所有的业务都必须实时处理.不是所有的请求都必须实时反馈结果给用户.不是所有的请求/处理都必须100%处理成功.不知道谁依赖"我"的处理结果.不关心其他系统如何处理后续业务.不需要强一致性,只需保证最终一致性即可.想要保证数据处理的有序性:此时你应该考虑使用队列来解决这些问题.在实际开发时我们经常使用队列进行异步处理

【转】聊聊高并发系统之队列术 京东技术 www.toutiao.im

声明:本文转载自微信公众号"开涛的博客",转载务必声明.   队列在数据结构中是一种线性表,从一端插入数据,然后从另一端删除数据.本文目的不是讲解各种队列算法,而是在应用层面讲述使用队列能解决哪些场景问题.   在我开发过的系统中,不是所有的业务都必须实时处理.不是所有的请求都必须实时反馈结果给用户.不是所有的请求/处理都必须100%处理成功.不知道谁依赖"我"的处理结果.不关心其他系统如何处理后续业务.不需要强一致性,只需保证最终一致性即可.想要保证数据处理的有序

高并发系统之HTTP缓存

简介 最近遇到很多人来咨询我关于浏览器缓存的一些问题,而这些问题都是类似的,因此总结本文来解答以后遇到类似问题的朋友.   因本文主要以浏览器缓存场景介绍,所以非浏览器场景下的一些用法本文不会介绍,而且本文以chrome为测试浏览器.   浏览器缓存是指当我们使用浏览器访问一些网站页面或者http服务时,根据服务端返回的缓存设置响应头将响应内容缓存到浏览器,下次可以直接使用缓存内容或者仅需要去服务端验证内容是否过期即可.这样的好处可以减少浏览器和服务端之间来回传输的数据量,节省带宽提升性能.  

高并发系统之限流特技:有了它,京东6.18如虎添翼!

在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹:而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开:而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀.抢购).写服务(如评论.下单).频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流. 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到

高并发系统数据幂等的解决方案_MsSql

前言 在系统开发过程中,经常遇到数据重复插入.重复更新.消息重发发送等等问题,因为应用系统的复杂逻辑以及网络交互存在的不确定性,会导致这一重复现象,但是有些逻辑是需要有幂等特性的,否则造成的后果会比较严重,例如订单重复创建,这时候带来的问题可是非同一般啊. 什么是系统的幂等性 幂等是数据中得一个概念,表示N次变换和1次变换的结果相同. 高并发的系统如何保证幂等性? 1.查询 查询的API,可以说是天然的幂等性,因为你查询一次和查询两次,对于系统来讲,没有任何数据的变更,所以,查询一次和查询多次一

高并发系统数据幂等的解决方案

前言 在系统开发过程中,经常遇到数据重复插入.重复更新.消息重发发送等等问题,因为应用系统的复杂逻辑以及网络交互存在的不确定性,会导致这一重复现象,但是有些逻辑是需要有幂等特性的,否则造成的后果会比较严重,例如订单重复创建,这时候带来的问题可是非同一般啊. 什么是系统的幂等性 幂等是数据中得一个概念,表示N次变换和1次变换的结果相同. 高并发的系统如何保证幂等性? 1.查询 查询的API,可以说是天然的幂等性,因为你查询一次和查询两次,对于系统来讲,没有任何数据的变更,所以,查询一次和查询多次一

【转】聊聊java高并发系统之异步非阻塞

在做电商系统时,流量入口如首页.活动页.商品详情页等系统承载了网站的大部分流量,而这些系统的主要职责包括聚合数据拼装模板.热点统计.缓存.下游功能降级开关.托底数据等等.其中聚合数据需要调用其它多个系统服务获取数据.拼装数据/模板然后返回给前端,聚合数据来源主要有依赖系统/服务.缓存.数据库等:而系统之间的调用可以通过如http接口调用(如HttpClient).SOA服务调用(如dubbo.thrift)等等.   在Java中,如使用Tomcat,一个请求会分配一个线程进行请求处理,该线程负