如何设计一个好的架构一直是架构师的职责和目标,在面临复杂的系统无论是从0开始,还是重构,其实都可以在一些通用的原则下指导,以下就是我总结一些高并发和高可用的设计简单原则。
高并发
要实现高并发,首先我们得先了解什么是高并发?通常意义上高并发对于后端程序员而言,就是Server承载的QPS或者TPS,其实不完整,应该说整个服务能承载的QPS。
为什么?在很多场景下我们是以服务形式提供,比如我们说服务需要支持10W+的QPS,即使服务的某个模块因为客观原因不能达到(比如直接写mysql如果不优化,性能是不可能达到),但是我们可以通过优化服务的架构,实现能支持10W+的QPS,那如何实现,下面就一起探索。
1、无状态
无状态的服务是设计高并发架构的一个建议原则,因为如果服务是无状态的,我们可以实现水平扩展,比如:接入层服务,可以设计为无状态的。
如果面临一些服务本身不能实现无状态的,那就应该尽量在业务层实现扩展,比如:服务需要保持用户连接状态,那应该设计通过存储用户session的方式,来实现服务水平扩展。
2、微服务拆分
在系统设计初期,可能我们为了简单设计一个功能大而杂的系统,但是一旦业务稳定以后,我们就应该考虑按照微服务的架构拆分系统。
系统维度:可以按照系统层级拆分,比如接入层,存储层,经常变更的业务逻辑层等;
功能维度:一个大的系统是由多个功能组成,很多功能已经稳定,就应该将稳定的功能独立为服务,而不是和业务层柔在一起,比如登录鉴权;
读写维度:读写分离是扩展高并发的一种常用的方案,对于大部分系统读多写少,就可以尽快拆分;
AOP维度:AOP是面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率;
3、服务化
系统服务化也是高并发的一个原则,设计之初应该将通用功能考虑到各个模块,比如我们可以建立一个RPC Server
框架,其中RPC Server
框架自带各个功能:服务注册,服务发现,链路跟踪,监控上报,配置更新等,这样对基于RPC Server
开发的模块,都是一个功能完整的服务。
4、消息队列
使用消息队列主要是用来解耦一些非同步逻辑或者订阅服务,比如瞬时流量过高,异步服务,消费解耦等。
削峰填谷:在很多系统中存在瞬时流量过高,平峰时候流量很低,这时就可以考虑使用消息队列,牺牲时延或者一致性解决瞬时流量冲垮下游。
服务解耦:服务之间不一定是强依赖的关系,或者模块非关键路径,就可以通过消息队列解耦,比如日志分析模块,关键模块可以通过消息队列上报日志,然后由日志分析模块消费日志,生成报表。
5、数据异构
服务在设计之初可能都使用统一的存储方案,但是到了系统庞大的时候,往往需要将数据拆分,不同的业务系统可能需要使用不同存储层,比如搜索需要使用ES,强一致性需要使用mysql,离线分析需要使用hbase等,所以在追求高并发的前提下,我们应该尽可能选择合适的存储架构,而不是一直使用mysql。
6、缓存银弹
缓存一直是我们设计架构中的银弹,从客户端到服务端都是如此;
客户端缓存:往往在设计架构时候,不止考虑服务端,需要结合客户端的一些特性来考虑高并发,比如浏览器支持Cache-control,Expires,etags等HTTP特性,我们就应该好好利用;对于APP某些启动才需要的数据,我们应该下发给客户端缓存起来,减少拉取的次数;
CDN缓存:静态资源大家应该都会考虑用CDN,但是对于一些配置化资源,我觉得应该也使用CDN+动态请求,CDN拉取有一定的失败率,如果拉取CDN失败了,可以重新走动态请求拉取,这样可以减少后端压力同时保障成功率;
接入层缓存:接入层缓存主要考虑local cache,由于接入层一般设计为无状态的,方便扩展可以搭配中心cache+local cache,减少中心cache的请求压力;
业务层缓存:业务层缓存可以使用中心cache+local cache,如果本身业务拆分的比较细,又减少业务系统开发的复杂度,也可以直接使用中心cache(如果使用local cache需要考虑到local cache与中心cache的一致性问题);
分布式缓存:中心cache往往不是单机形式,而是以集群形式存在,同时会涉及一致性问题,所以这种场景下就需要业务开发者+架构师一起考虑如何拆分的方案,一般是具体场景具体分析;
7、并发设计
并发设计是提升服务QPS最快的方案,比如C++中我们可以使用协程,golang就直接使用goroutine等,server的开发应该后端程序员接触很多,这里就不过多介绍,具体一些设计可以参考开源(libco,gonet,netpoll等框架);
高可用
在考虑服务高可用之前,我们应该多思考墨菲定律:
-
任何事都没有表面看起来那么简单; -
所有的事都会比你预计的时间长; -
会出错的事总会出错;
这里告诉我们,对于系统架构的设计一定要尽量考虑全面才能实现99.9999…%。
1、降级
在一个大的系统中,一定存在服务的优先级问题,谁是核心系统,谁是可以降级服务的,在设计之初我们就应该考虑,然后通过预埋的熔断开关或者服务发现机制临时调整。
(1)每个模块可以接入熔断开关,当紧急情况下可以通过中心化的配置下发熔断开关关闭某些服务或者调整权重;
(2)读写降级,某些服务在存储出现瓶颈时,可以先写本地cache,然后当存储恢复后再异步刷入;
(3)流量降级,某些服务如果异常且不能紧急扩容,可以在服务发现端在上游把模块踢掉,减少流量打爆服务的风险;
(4)业务降级,重要的业务优先保障,比如登录服务,首屏展示等;
2、限流
限流是作为一个后端程序员经常面试的问题,具体的实现方式有很多(固定窗口算法,滑动窗口算法,滑动日志算法,漏桶算法,令牌桶算法等),就不详细介绍,这里只介绍我们应该如何限流。
(1)当前系统能承载的最大峰值95%左右开始限流,防止瞬时流量由于不均衡打垮某些资源,70%~80%就需要开始预警,告诉开发者需要关注服务的健康度;
(2)恶意流量我们应该动态下发白名单,根据算法策略阶梯限流;
(3)对于访问下游我们可以使用熔断器,这个一般在RPC Server
自带的功能,防止下游由于流量过大,拖垮上游服务;
(4)根据各个模块的负载情况,定时上报或者计算出模块的极限承受峰值,定期更新限流的QPS;
3、隔离
隔离主要解决机房或者服务间相互影响的问题,主要考虑这几个方面:
(1)进程或者线程隔离,防止某个空指针或者内存泄露相互影响;
(2)集群/机房隔离,对于重要的系统设置两地三中心方案,这里不止在业务层,还需要考虑存储层和接入层;
(3)读写隔离和动静隔离,在异常情况下保障部分服务可用;
4、可回滚
服务变更在业务系统中是常见的,现在devops的发展,我们的系统可能一天会变更几十次,这种情况下出错肯定是难免的,但是我们应该将出错降低到最小化。
(1)灰度发布,可以采用蓝绿或者金丝雀发布,现在使用k8s发布和回滚比较方便,这里就需要考虑出现问题时候将依赖项也能实现回滚;
(2)流量迁移,在系统设计过程中,服务往往和存储层联系,那回滚就是一个问题?因此对于发布过程中,可以通过染色实现流量迁移到新版本集群中,从而在出现问题时候可以回滚或者清理数据;
5、其他
(1)防重设计,要提高可用性,客户端或者上游在网络异常时候,会考虑重试,那如何在不影响业务情况下可以无脑重试呢?那就是根据字段进行防重放,特别在订单或者支付中需要重点考虑;
(2)幂等设计,系统设计为幂等不仅能简化调用者的架构,也能提高可用性,比如事务处理,如果某个功能已经锁住,我们就应该返回错误信息或者告诉执行相同的结果;
(3)状态机设计,状态机的出现是为了将复杂对象的状态变化进行建模,采取工程化的方式来处理,方便理解与沟通,在系统中使用状态机能固定迁移状态,减少系统不一致的问题;
原文始发于微信公众号(周末程序猿):技术总结|高并发高可用设计原则
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/169407.html