9.软件架构设计:大型网站技术架构与业务架构融合之道 --- 高可用与稳定性

Posted enlyhua

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了9.软件架构设计:大型网站技术架构与业务架构融合之道 --- 高可用与稳定性相关的知识,希望对你有一定的参考价值。

第9章 高可用与稳定性 
	"高并发" 是为了让系统变得 "有效率",高可用和稳定是是为了让系统变得 "更稳定"。

9.1 多副本 
	对于网关和应用服务器这种无状态的服务,做多副本比较简单,加机器就行;但对于缓存和数据库这种有状态的,如果做多个副本,会存在数据同步问题。

	1.本地缓存多副本
		一种常用的方法是利用消息中间件(如kafka)的Pub/Sub机制,每台机器都订阅消息。一条消息发送出去,每台机器都会收到这台消息,然后更新自己的本地缓存。

	2.Redis多副本
		Redis Cluster 提供了 master-slave 之间的复制机制,当master宕机后可以切换到slave。当然,切换需要间隔时间(一般为十几秒),同时因为是异步复制,
	切换之后可能会丢失少量的数据。

		如果是宕机redis,则需要业务人员自己部署两套,自己做双写和切换。

		补充:无论是本地缓存,还是redis集中式缓存,既然本身的定位是 "缓存",意味着业务场景对数据不是强一致性的要求,所以此处主要考虑的是 "高可用性",而没有
	数据一致性的保证。

	3.mysql多副本
		对于mysql,master-slave 之间用的最多的是异步复制或半异步复制,同步复制性能太差。

		对于异步复制,如果slave超时后还未返回,也会退化为异步复制。所以,无论是异步复制,还是半异步复制,都不能百分百的保证master和slave中的数据完全一致。

		实际上,一般都会选择牺牲一定的数据一致性来保证可用性。少量的数据不一致,可以通过后续的人工修复解决;如果服务不可用,尤其在流量大的时候,带来的损失往往
	远大于少量数据不一致的损失。

		另外,还会有一些监控,如果发现某个slave延迟太大,则直接摘除,避免延迟太大的slave被选为主库。

	4.消息中间件多副本
		对于kafka类的消息中间件,一个 Partition 通常至少会指定三个副本,为此kafka专门设计了一种 ISR 的算法,在多个副本之间做消息的同步。

9.2 隔离、限流、熔断和降级 
	1.隔离
		隔离是指将系统或资源分隔开,在系统发生故障时能够限定传播范围和影响范围,即发生故障后不会出现滚雪球效应,从而把故障的影响限定在一个范围内。

		1.数据隔离
			从数据的重要性角度来说,一个公司或业务的数据肯定有非常重要,次重要,不重要之分,在数据库的存储中,把这些数据所在的物理库彻底分开。这也对应着
		业务的拆分和分库。从这个角度来说,业务的拆库和数据的隔离,其实是从不同的角度说同一个事情。

		2.机器隔离
			对一个服务来说,有很多调用者。这些调用者也有一个重要性等级排序。对于那些最核心的几个调用者,可以专门为其准备一组机器,这样其他的调用者不会
		影响该调用者的服务。

			又或者,本来是一个核心服务,因为某种原因在上面增加了一个新功能(新接口),这个新功能只是为某个调用方使用,可以把调用方隔离出来,不影响现有功能。

			成熟的RPC框架往往有隔离的功能,根据调用方的标识(ID),把来自某个调用方的请求都发送到一组固定的机器中,无需业务人员写代码,用一个简单的配置即可
		搞定。

		3.线程池隔离
			首先要注意设置客户端的超时时间,如果超时时间很长,一旦某个服务延迟很大,就容易打垮服务。为此,可以使用线程池隔离,为每个rpc调用单独准备一个
		线程池(一般2~10个线程)。当线程池中没有空闲线程,并且线程池内部的队列已经满了的情况下,线程池会直接抛出异常,拒绝新请求,从而保证线程不会被阻塞。

			还有一个场景,比如一个rpc服务对外提供了很多接口,绝大部分接口都处理的很快,有极个别接口的业务逻辑很复杂,处理的很慢,则在rpc服务内部可以为其
		准备单独的一个线程池。这样一来,虽然这个接口很慢,但只是它自己慢,不会影响其他。

		4.信号量隔离
			信号量隔离是 Hystrix 提出的另外一种隔离方法,它比线程池隔离要轻量。一个机器能开的线程数是有限的,线程池太多会导致线程太多,线程切换的开销很大。
		而使用信号量隔离不会额外增加线程池,只在调用线程内部执行。信号量本质上是一个数字,记录了当前访问某个资源的并发线程数,在线程访问资源之前获得该信号量,
		当访问结束时,释放信号量。一旦信号量达到最大的阈值,线程获取不到该信号量,会丢弃请求,而不是阻塞在那里等待信号量。

	2.限流
		限流可以分为技术层面的限流和业务层面的限流。技术层面的限流比较通用,各种业务场景都可以用到;业务层面的限流需要根据具体的业务场景做开发。

		1.技术层面的限流
			一种是限制并发数,也就是根据系统的最大资源进行限制,比如数据库连接池,线程池,nginx的 limit_conn模块;另外一种是限制速率(qps),比如Guava的
		RateLimiter,nginx的 limit_req 模块。

			限制速率的这种方式对于服务的接口调用非常有用。比如通过压力测试可以知道服务的qps是2000,就可以限流为2000qps。当调用方超过了这个数字,会直接拒绝
		提供服务。这样一来,即使突然有大量请求进来,服务也不会被压垮,虽然部分请求被拒绝了,但保证了其他服务正常运行。

		2.业务层面的限流
			比如秒杀系统,一个商品的库存只有100件,现在有2w人抢购,没必要放2w人进来,只需要放前500个进来,后面的人直接返回已售完即可。

			针对这种场景,可以做一个限流系统,或者叫售卖的资格系统(票据系统),票据系统放了500张票,每来一个人,领一张票据。领到票据的人进入后面的业务系统进行
		抢购;对于抢不到的人,则返回售完。

			在具体实现上,有的用redis,有的用nginx+lua。

		3.限流算法
			限制并发数的计算原理很简单,系统只需要维护正在使用的资源数或空闲数,比如数据库的连接数,线程池的线程数。限制速率的算法稍微复杂点,常用的有漏桶
		算法和令牌桶算法。

			漏桶算法:
				1.漏桶的容量是固定的,流出的速率是恒定的;
				2.流入的速率是任意的;
				3.如果桶是空的,则不需流出;
				4.如果流入的数据包超出了桶的容量,则流入的数据包溢出了(被丢弃),而漏桶的容量不变。

			令牌桶算法:
				1.令牌桶的容量也是固定的,向里流入令牌的速率是恒定的;
				2.当令牌桶满时,新加入的令牌会被丢弃;
				3.当一个请求达到后,从桶中取出一个令牌。如果能取到令牌,则处理该请求;
				4.如果取不到令牌,则该请求要么被丢弃,要么排队。

			区别:
				对比两个算法会发现,二者的原理刚好相反,一个是流出速率保持恒定,一个是流入速率保持恒定。二者的用途有一定区别,令牌桶限制的是平均流入速率,而
			不是瞬时速率,因为可能出现一段时间没有请求来,令牌桶塞满了令牌,然后短时间突发流量过来,一瞬时从桶里拿几个令牌出来;漏桶有点类似于消息队列,起到了
			削峰的作用,平滑了突发流入速率。

	3.熔断
		熔断有两种策略:
			1.一种是根据请求失败率
				对于客户端调用的某个服务,如果服务在短时间内大量超时或者抛错,则客户端直接开启熔断,也就是不再调用此服务。然后过了一段时间,再把熔断打开,
			如果还是不行,则继续熔断。这就是 "快速失败(Fail Fast)" 策略。

			2.一种是根据请求响应时间
				当资源的平均响应时间超过阈值后,资源进入准降级状态。接下来如果持续进入5个请求,且他们的RT持续超过该阈值,那么在接下来的时间窗口内,对这个
			方法的调用都会自动的返回。

		与限流对比:限流是服务端限流,根据其能力上限设置一个过载保护;而熔断是调用端对自己做的一个保护。

		能熔断的服务肯定不是核心链路上的必选服务。如果是的话,则服务如果超时或者宕机,前端就不能用了,而不是熔断。所以,说熔断其实也是降级的一种方式。


	4.降级
		降级是一种兜底的方案,是在系统出故障之后的一个尽力而为的措施。相比限流,熔断两个偏技术的词汇,降级则是一个更加偏业务的词汇。

		因为在现实中,虽然任何的一个业务或者系统都有很多功能,但并不要求这些功能一定100%可用,或者完全不可用。其中存在一定的灰度空间。

		比如对于微信或者QQ,有文字通信,语音通信,视频通信,对带宽的要求是从小到大。网络发生故障时,可以优先保证文字通信可用。总之,会尽最大的努力提高服务,
	哪怕是有损服务,也比完全不提供服务强。

		还有电商的千人前面,可能靠的是推荐系统。如果推荐系统挂了,可以准备一个静态的商品列表。

		降级不是一个纯粹的技术手段,而是根据业务场景具体分析,看哪些功能可以降级,降级到什么程度,哪些宁愿不可用也不能降级。


9.3 灰度发布与回滚 
	如果一个系统在线上的代码不动,不更新,理论上可以稳定的一直运行下去。频繁的变更是导致系统不稳定的一个直接因素。既然无法避免系统变更,我们能做的就是让这个过程
尽可能的 平滑,受控,这就是灰度与回滚策略。
	
	1.新功能上线的灰度
		当一个新功能上线时,可以先将一部分流量导入这个新的功能,如果验证没有问题,再一点点增加流量,最终让所有流量都切换到这个新功能上。

		具体办法有很多,比如可以按 user_id 划分流量,按 user_id 的最后几位数字对用户进行分片,一片片的灰度把流量导入到新功能上;或者用户有很多属性,标签,
	按其中的标签设置用户白名单,再一点点导入流量。

	2.旧系统重构的灰度
		同上。

	3.回滚
		一种是安装包回滚,这种办法比较简单,不需要额外开发代码,发现线上有问题,统一部署之前的安装版本;另一种是功能回滚,在开发新功能的时候,也开发了相应的配置
	开关,一旦有问题,则关闭开关,让所有流量都进入老的系统。


9.4 监控体系与日志报警 
	1.监控体系
		1.资源监控	
			如cpu,内存,磁盘,带宽,端口等。

		2.系统监控
			如:
				1.最前端url访问的失败率以及具体某次访问的失败链路;
				2.rpc接口的失败率以及具体某次请求的失败链路;
				3.rpc接口的平均响应时间,最大响应时间,95线,99线;
				4.db 的 long sql;
				5.如果使用的是 Java,JVM的 young GC,full GC 的回收频率,回收时间等。

		3.业务监控
			根据业务具体分析。把业务系统再扩展一下,就变成了对账系统。

	2.日志报警
		如果说业务指标的监控是基于统计数据的一个监控,日志报警则是对某一次具体的请求的处理过程的监控。

		在输出日志的过程中,最容易出现的问题可能有:
			1.日志等级不分
			2.关键日志漏打

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

以上是关于9.软件架构设计:大型网站技术架构与业务架构融合之道 --- 高可用与稳定性的主要内容,如果未能解决你的问题,请参考以下文章

《软件架构设计:大型网站技术架构与业务架构融合之道》思维导图

3.软件架构设计:大型网站技术架构与业务架构融合之道 --- 语言

14.软件架构设计:大型网站技术架构与业务架构融合之道 --- 业务架构思维

13.软件架构设计:大型网站技术架构与业务架构融合之道 --- 业务意识

7.软件架构设计:大型网站技术架构与业务架构融合之道 --- 框架软件与中间件

2.软件架构设计:大型网站技术架构与业务架构融合之道 --- 架构的道与术