当前位置: 首页 > 科技观察

你这狗屁叫高可用

时间:2023-03-13 06:22:35 科技观察

大家好,我是鲲哥。今天就来聊聊互联网三高(高并发、高性能、高可用)中的高可用。看完这篇文章,相信一定能解决你的问题。大部分关于高可用性设计的困惑前言高可用性(HA)的主要目的是保证“业务连续性”,即在用户眼中,业务始终正常(或基本正常)对外提供服务.高可用主要是针对架构的,所以要做高可用首先要设计好架构,第一步我们一般采用分层的思想将一个庞大的IT系统拆分成应用层、中间件、数据存储层等独立的层,然后将每一层分成更细粒度的组件。第二步是让各个组件对外提供服务。毕竟每个组件都不是孤立存在的,需要相互配合才能对外提供服务。才有意义。为保证架构的高可用,需要确保架构中的所有组件及其暴露的服务都是为高可用而设计的。任何不具备高可用性的组件或其服务都意味着系统存在风险。那么这么多组件如何设计高可用呢?事实上,任何组件都需要在没有“冗余”和“自动故障转移”的情况下实现高可用。众所周知,单点是高可用的大敌,所以组件一般都是以集群的形式存在(至少两台机器),这样只要某台机器出现问题,集群中的其他机器可以随时替换它。这就是“冗余”。简单计算一下,假设一台机器的可用性是90%,两台机器组成的集群的可用性是1-0.1*0.1=99%,那么显然冗余机器越多,可用性就越高。但仅有冗余是不够的。如果机器出现问题,人工切换既费时费力,又容易出错。因此,我们还需要借助第三方工具(即仲裁器)的力量来实现“自动”故障转移。实现近实时故障转移的目标,近实时故障转移是高可用性的主要含义。什么样的系统才能称为高可用?业界一般用几个九来衡量系统的可用性。系统可用性级别如下%停机时间/年停机时间/月停机时间/周停机时间/天不可用90%36.5天73小时16.8小时144分钟基本可用99%87.6小时7.3小时1.68小时14.4分钟高级可用99.9%8.76小时43.8分10.1分1.44分高可用99.99%52.56分4.38分1.01秒8.64秒非常高可用99.999%5.26分26.28秒6.06秒0.86秒一般实现两个9很简单,毕竟宕机14分钟每天都严重影响了生意,这样的公司迟早要倒闭,大厂一般要求4个9,其他要求高的业务要达到5个9以上,比如电脑故障所有的火车都停了,那么就有将数以万计的计划之人的正常生活如果受到阻碍,这种情况需要五个九以上。下面我们就来看看架构中的各个组件是如何借助“冗余”和“故障自动切换”实现高可用的。互联网架构分析目前互联网大部分都会采用微服务架构。常见的架构如下:可以看到架构主要分为以下几层。接入层:主要是F5硬件或者LVS软件来承载所有的流量入口。负责根据url进行流量分配、限流等网关:主要负责流量控制、风控、协议转换等站点层:主要负责调用会员、推广等基础服务,组装json等数据和返回给客户端基础服务:其实和站点层属于微服务,是一个层次关系,但是基础服务属于基础设施,可以被上层业务层服务器调用。存储层:即DB,如MySQL、Oracle等,一般由基础服务调用返回Site级中间件:ZK、ES、Redis、MQ等,主要起到加速访问的作用到数据和其他功能。下面,我们将简要介绍各个组件的功能。上文提到,要实现整体架构的高可用,需要实现每一层组件的高可用。接下来我们看看各层组件是如何实现高可用访问层&反向代理层的。这两层的高可用是和keepalived相关的,所以我们把外面的世界结合起来看,两个LVS以master和backup的形式提供服务。注意只有master在工作(也就是此时的VIP对master生效),master宕机后其他backup会接管master的工作。那么backup如何知道master是否正常呢?答案是通过keepalived在主备机上都安装keepalived软件。启动后,它们会通过心跳检测彼此的健康状态。一旦master宕机,keepalived会检测到,backup会自动转为master对外提供服务。此时VIP地址(即图中的115.204.94.139)对备份生效,也就是我们常说的“IP漂移”。这样就解决了LVS的高可用问题。keepalived的心跳检测主要通过发送ICMP报文进行检测,或者使用TCP端口连接扫描检测。同样的,它也可以用来检测Nginx暴露的端口,这样如果某个Nginx出现异常,Keepalived也可以检测到并从LVS可以转发的服务列表中移除。借用第三方工具keepalived,同时实现了LVS和Nginx的高可用。同时,当出现故障时,可以将宕机信息发送到相应开发者的邮箱,以便他们及时收到通知。真的很方便。Keepalived应用广泛,下面我们会看到它也可以用在MySQL上,实现MySQL的高可用。微服务接下来,我们来看看“网关”、“站点层”、“基础服务层”。这三个一般就是我们所说的微服务架构组件。当然,这些微服务组件也需要通过一些RPC框架如Dubbo来支持通信,微服务必须做到高可用,也就是说dubbo等RPC框架也必须提供支持微服务高可用的能力。下面以dubbo为例,看看它是如何实现高可用的。下面开始简单的看一下dubbo的基本架构也很简单。首先Provider(服务提供者)向Registry(注册中心,如ZK或Nacos等)注册服务,然后Consumer(服务消费者)从注册中心订阅和拉取。提供商服务列表。Consumer获得服务列表后,可以根据自己的负载均衡策略选择其中一个Provider向其发送请求。当其中一个Provider不可用时(离线或GC阻塞等),会被注册中心及时注册。当监听(通过心跳机制)到达时,也会及时推送给Consumer,以便Consumer将其从可用的Provider列表中移除,实现故障的自动转移。不难看出,注册中心起到了类似keepalived的作用下面我们来看看ZK、Redis等这些中间件是如何实现高可用的。上一节ZK微服务,我们提到了注册中心,那么我们就以ZK(ZooKeeper)为例,看看它是如何工作的。如何实现高可用,我们先来看下它的整体架构图。Zookeeper中的主要角色如下。Leader:即领导者。集群中只有一个Leader,主要承担以下功能。事务请求的唯一调度者和处理者,保证集群事务处理的顺序,所有Follower写请求都会转发给Leader执行,保证事务一致性。集群中各个server的调度器:处理完事务请求后,会同步广播数据给各个Follower。计算成功的跟随者写入次数。如果超过半数的follower写成功,leader会认为写请求提交成功,并通知所有follower提交写操作,保证即使集群crash之后恢复或重启,也不会写操作迷路了。.Follower:处理客户端非事务性请求,将事务性请求转发给leader服务器参与事务请求Proposal投票(需要半数以上服务器通过才能通知leader提交数据;Leader发起的提案需要Followertovote)参与Leader选举的投票画外音:Zookeeper3.0之后新增了一个Observer的角色,不过和这里讨论的ZK高可用关系不大。为了简化问题,省略。可以看出,由于只有一个Leader,很明显这个Leader存在单点危险,那么ZK是如何解决这个问题的呢?首先,Follower和Leader会使用心跳机制来保持连接。如果Leader出现问题(宕机或FullGC等原因无法响应),Follower将无法感知Leader的心跳,会认为Leader出了问题,发起投票选举,最后在多个Followers中选出一个Leader(ZookeeperAtomicBroadcast,这里主要用到ZAB协议,是专门为ZK设计的支持崩溃恢复的共识协议。选举的细节不是重点这篇文章,这里就不详述了。除了ZAB协议,Paxos、Raft等协议算法是业界常用的,也可以用在Leader选举中,即在分布式架构中,这些协议算法承担了“第三方”的角色orarbitrator,为了承担故障的自动转移RedisRedis的高可用需要根据其部署方式来查看。主要分为两种主从模式:“主从模式”和“集群分片模式”。先来看看主从模式,架构下面的主从模式主从模式是一主多从(一个或多个从节点),其中主节点主要负责读写,而然后将数据同步到多个从节点。发起读请求,可以减轻master节点的压力,但是像ZK一样,由于只有一个master节点,存在单点危险,所以必须引入第三方仲裁机制来判断master是否节点已关闭并且主节点已关闭。关机后,迅速选出一个slave节点作为master节点。这个第三方仲裁者在Redis中一般被称为“哨兵”。当然哨兵进程本身也有可能挂掉,所以为了安全起见,需要部署多个哨兵(也就是哨兵集群)。这些哨兵通过gossip(谣言)协议接收主服务器是否下线的信息,并在确定主节点宕机后使用Raft协议选举新的主节点Cluster。分片集群的主从模式看似完美,但是存在以下问题难以降低主节点的写压力:因为只有一个主节点可以接收写请求,如果写请求高高并发的情况下,写请求可能会把master节点的网卡填满,导致master节点无法对外服务。主节点的存储容量受单机存储容量的限制:因为不管是主节点还是从节点,所有的缓存数据都是全量存储的,所以随着业务量的增长,缓存的数据很可能直线上升,直到达到存储瓶颈同步风暴:因为数据是从master同步到slave的,如果有多个slave节点,master节点的压力会很大为了解决以上master-slave模式的问题,Sharded集群应运而生。所谓分片集群就是数据分片。每个分片数据由对应的主节点读写。这样就有多个master节点分担写入压力,每个节点只存储部分数据也解决了单机存储瓶颈问题,但是需要注意的是每个master节点存在单点问题,所以有必要为每个主节点做高可用。整体架构如下,原理也很简单。客户端执行redis读写命令后,首先会计算key得到一个值,如果该值在对应master的范围内(一个一般每个数称为一个slot,Redis一共有16384个slot),然后将这个redis命令发送给对应的master执行,可以看到每个master节点只负责处理部分redis数据,以及同时为了避免每个master的单点问题,还配备了多个slave节点组成集群。当主节点宕机时,集群会使用Raft算法从从节点中选出一个主节点ES。让我们来看看ES。如何实现高可用,在ES中,数据以分片(Shard)的形式存在,如下图,一个节点中的索引数据被分成三个分片进行存储,但是如果只有一个节点,则很明显和Redis的主从架构一样存在单点问题。如果这个节点挂了,ES也会挂掉,所以显然需要创建多个节点。分片的优势就体现出来了。分片后的数据可以分布存储在其他节点上,大大提高了数据的水平扩展能力。同时每个节点都可以承担读写请求,避免了ES的写机制与Redis和MySQL的主从架构有些不同(后两者直接写到master节点,而ES没有),所以这里稍微解释一下ES的工作原理首先说下节点的工作机制。节点(Node)分为主节点(MasterNode)和从节点(SlaveNode)。主节点的主要职责是负责集群层面的相关操作,管理集群的变化,比如创建或删除索引,跟踪哪些节点是集群的一部分,并决定哪些分片分配给相关节点.主节点只有一个,一般由Bully-like算法选举产生。如果master节点不可用,其他slave节点也会通过这种算法进行选举,实现集群的高可用。任何节点都可以接收读写请求,实现负载均衡。下面说一下分片的工作原理。Sharding分为primaryshards(图中的P0、P1、P2)和replicashards(ReplicaShard,即图中的R0、R1、R2),primaryshard负责数据的写入操作,所以虽然任何node可以接收读写请求,如果本节点收到write请求,没有写入数据的primaryshard,则node会将写请求调度到primaryshard所在的节点。写入主分片后,主分片会将数据复制到其他节点的副本分片。以有两个副本的集群为例,写入操作如下。MQES使用数据分片来提高高可用性和水平可伸缩性。该思想也被应用到其他组件的架构设计中。下面以MQ中的Kafka为例,再看看Kafka的高可用设计,数据分片的应用。一旦一个Partition不可用,可以从followers中选出一个leader来继续服务。但是与ES中的数据分片不同,followerPartition是一个冷备,也就是说一般情况下不会对外服务。只有leader挂了之后,才能从followers中选举出leader,才可以对外提供服务。接下来我们来看最后一层,存储层(DB)。这里我们以MySQL为例简单讨论一下它的高可用设计。其实,如果你看过上面的高可用设计,你会发现MySQL的高可用也不过如此,思路也大同小异。和Redis类似,也分为主从和分片(也就是我们常说的分库分表)。这种主从架构类似于LVS。一般使用keepalived来实现高可用。如下图,如果master挂了,Keepalived会及时发现,于是从库升级主库,VIP也会“漂移”到原来的从数据库生效,所以配置的MySQL地址项目中的每个人一般都是VIP,以确保高可用性。数据量大之后,会分库分表,所以会有多个master,就像Redis分片集群一样,每个master需要配备多个slave。如下,有读者问为什么分库分表后还需要做主从。现在我想大家应该明白了,不是为了解决读写性能的问题,主要是为了实现高可用总结看完架构层面的高可用设计,相信大家会对高可用性“冗余”和“自动故障转移”的核心思想。观察上面架构中的组件,你会发现冗余的主要原因。因为只有一个master,为什么不能有多个master呢?不是不可以,但是在分布式系统中保证数据的一致性是非常困难的,尤其是节点很多的时候,数据之间的同步就更难了。这是一个很大的问题,所以大部分组件都是采用一个master的形式,然后在master和多个slave之间进行同步。大多数组件之所以选择一个master,本质上是一种技术上的权衡。把每个组件都做到高可用之后,是不是整个架构真的可以用了,不,这只能说是迈出了第一步,生产中还有很多突发情况会让我们的系统面临挑战,比如瞬时流量问题:比如我们可能会面临尖峰系统带来的瞬时流量激增,会压垮系统的承载能力。这种情况可能会影响到日常交易等核心环节,所以需要对系统进行隔离,比如部署一套独立的集群进行秒杀。安全问题:比如DDOS攻击,爬虫频繁请求,甚至删库跑路,导致系统拒绝服务代码问题:比如代码bug导致内存泄露,FullGC导致系统无法响应等。部署问题:在发布过程中突然停止当前正在运行的服务是不可接受的,需要优雅关闭才能顺利发布第三方问题:比如我们以前的服务依赖于第三方系统,而第三方可能会出现影响我们核心业务的不可抗力问题:机房停电,需要做容灾异地办公。我们业务之前,因为机房故障导致服务4小时不可用,所以除了架构的高可用,我们还需要采取系统隔离、限流、熔断、风控等措施,对关键操作进行降级、限制操作员权限,保证系统的可用性。降级,这是保证系统可用性的常用措施。举几个例子,我们之前曾接到过一个第三方出资人,由于自身原因导致借贷功能出现问题而无法借贷。为了避免引起用户的恐慌,所以当用户申请第三方贷款时,我们返回了一份“为了提高您的信用额度,出资方正在升级系统”之类的副本,避免了客户的投诉。企业的首选不是查看日志排查问题,而是自动为用户降低码率。因为相对于画质的降低,卡住看不到对用户来说显然更痛苦。在双十一高峰期,我们停止了用户注册、登录等非核心功能,以保证下单等核心流程的顺畅。我们最好能够提前防御,在系统出现问题之前将其扼杀在摇篮中,所以我们需要做单元测试、全链路压力测试等来发现问题,还需要对CPU进行监控,threadcount等,当达到我们设置的阈值时,会触发报警,以便我们及时发现并修复问题(我们公司之前也遇到过类似的生产事故审查,大家可以看看),另外,在做好单元测试的前提下,还是有可能因为代码中潜在的bug导致上线问题,所以我们需要封网(也就是不允许关键时候(比如双十一期间)发布代码)。另外,我们需要在事故发生后快速定位问题,快速回滚,这就需要记录每次发布时间,发布人等,这里的发布不仅包括项目的发布,还包括配置中心的发布.画外音:上图是我们的发布记录,可以看到代码的变更、回滚等,这样发现问题可以一键回滚。最后用一张图总结高可用的常用方法