时光如风,一眨眼就沉浸在这个行业多年。见过无数厉害的人,也见过越来越多的挫折。前几天刚上班,就接到了面试一个毕业生的任务,让我感叹人与人之间的差距。他的水平绝对是工作多年的架构师。佩服~我们的主题是如何搭建一个可扩展、高可用、高可靠的大型网站。好吧,让我们开始吧。1.我想问一个问题。大家都知道,在今天的互联网上,数据量呈爆炸式增长,服务请求也在飙升。即使是一个很小的公司,也可能因为某个产品产生几十倍的流量。当然,这有时是一个梦。就这样。流量的增加意味着后端服务能力的提升。如何构建每秒处理GB数据、QPS超过数十万的大规模系统成为一个挑战。尤其是一些天才的秒杀点子,让这种流量更加变态,变幻莫测。有了有效的资源,系统如何保持良好的反馈?支持老板们的梦想?你如何解决?或者你有什么系统的经验可以分享给我吗?毕业生微微一笑:“我正好在这方面有了一些结论,可以多花点时间谈谈这个。”好吧,所有的耳朵。二、服务建设的重要指标首先要说明的是服务建设要注意的几个指标。有了目标,就有了方向。总的来说,我总结了四个“可用性”。我们要保证服务的可用性,也就是SLA的指标。只有服务能够正常响应,错误率保持在较低的水平,我们的服务才是有效的。这是正常的服务性能可用性和性能相辅相成,当服务性能高时,有限的资源可以支持更多的请求,可用性就会提高,所以服务性能优化是一项持续性的工作,平均每1ms的性能提升值得追求.可靠性.分布式服务中有很多组件,每个组件都可能出问题,影响面也不同。如何保证每个组件在运行时是高可靠的?如何保证数据的一致性是一个挑战。可观察性.为了获取服务优化的指标数据,我们要求我们的服务能够在一开始就保证服务的可观察性荷兰国际集团的设计。可以识别出宏观组件此类故障,可以为微观层面的性能优化提供依据。在HPA等自动化伸缩场景中,遥测数据甚至是自动化决策的唯一依据。对于一个服务来说,扩容的方式主要有两种:scale-up:纵向扩展;scale-out:横向扩展。垂直扩展通过增加单机配置来增加单节点的处理能力。这在一些业务场景中是非常必要的。但是我们的服务更多的是横向扩展。使用更多的机器来支持业务发展。只要服务满足横向扩展的能力和无状态的特性,剩下的就是堆硬件了。听起来不错,但实际上对整个架构的挑战是非常大的。毕业生的分析就像野鸡CTO的演讲,满嘴胡言乱语。我暗自点头,鼓励他继续深化提炼,搞出不一样的来。3.幂等性接口调用失败怎么办?在早期的互联网中,由于网络的原因,这种情况可能会比较严重。HTTP状态码504是代表网关超时的典型状态。第一个请求可能因超时而失败,但第二个请求可能会成功。现实中还是有不少接口需要严格重试,尤其是异步的加入,让重试显得尤为重要。但我们也必须考虑到由于快速重试而导致的重试风暴,因为超时本身可能意味着服务器不堪重负,我们没有任何理由火上浇油。因此,会有一个指数退避算法(exponentialbackoff)进行重试,直到实际结束请求进入异常处理流程。可以看出,由于超时和重试机制的引入,服务的幂等性变得异常重要。不仅支持单机重复调用,还保证了整个分布式集群环境下的多次重入。在数学上,它甚至有一个漂亮的函数公式:f(f(f(x)))=f(f(x))=f(x)一旦接口具有幂等性,它就可以容忍失败的能力。当我们因为偶尔的网络故障、机器故障导致少量服务调用失败时,我们可以通过重试和幂等轻松完成调用。对于查询操作,在数据集不变的情况下,自然是幂等的,不需要额外的处理。更具挑战性的是添加和更新操作。保证幂等性的技术手段有很多,比如使用数据库的唯一索引,使用预先生成的事务ID,或者使用token机制来保证调用的唯一性。其中,令牌机制被越来越多地使用。方法是在请求前先请求一个唯一的tokenId,后续调用的幂等性会围绕tokenId进行编程。4、健康检查自从k8s规范了健康检查之后,健康检查就成为了服务的必备选项。在k8s中分为livenessprobe和readinessprobe。Liveness探测器主要用于查明应用程序是否处于活动状态。它只显示应用程序本身的状态,不应依赖于其他外部系统的健康状态;readinessprobe指示应用程序是否准备好接受流量,如果应用程序实例的就绪状态未准备好,则流量不会路由到该实例。如果使用SpringBoot的执行器组件,通过health接口获取这部分功能会很容易。当容器或注册中心通过健康接口判断服务存在问题时,会自动将问题节点从节点列表中移除,然后通过一系列检测机制在服务恢复正常后挂掉。通过健康检查机制,可以防止流量被分派到错误的机器上。5.自动服务发现早期的软件开发人员了解服务启动机制不是因为他们想,而是因为他们不得不这样做。比如我要扩容一台机器,我需要先测试机器的生存能力,然后部署服务,最后在nginx等负载均衡软件中配置机器。通常,您必须查看日志以查看是否有任何流量流向该机器。借助微服务和持续集成,我们不再需要那么复杂的线上流程。我们只需要点击结构并在页面上发布,该服务就可以自动启动并被其他服务发现。注册中心在服务发现中扮演着非常重要的角色。相当于一个信息集中地,所有服务的启动和关闭都必须在这里上报;同样,如果我要调用一些服务,也需要去同一个注册中心查询。注册中心相当于一个中介,统一管理这些频繁的线上线下需求和查询需求,现在已经成为微服务必不可少的设施。这些查询需求可能会很频繁,所以也会在调用者本地保存一份,这样当注册中心出现问题时,不会因为大脑缺氧而导致大规模故障。使用副本,存在一致性问题。有注册中心,通过Pull更新信息,有数据一致性的有效性。有效性由具有Push(通知)机制的组件更好地处理,可以更快地感知服务变化。许多组件都可以充当服务注册中心,只要它具有分布式存储数据的能力和保持数据一致性的能力即可。比如Eureka、Nacos、Zookeeper、Consul、Redis,甚至数据库都可以胜任这个角色。6、在限流web开发中,tomcat默认线程池为200,当请求多了,没有新的线程处理请求时,请求会一直在浏览器端等待。表现形式是浏览器一直在旋转(不超过acceptCount),即使你请求一个简单的Helloworld。我们可以把这个过程看作限流。本质上就是给资源数量设置一个上限,超过这个上限的请求会被缓存或者直接失败。对于高并发场景下的限流,它有特殊的意义:主要用于保护底层资源。如果你想调用一些服务,你需要先获得调用它的权限。限流一般由服务提供者提供,限制调用者做事的能力。比如某服务为A、B、C提供服务,但是根据事先申请的流量预估,服务A的请求限制为1000/秒,服务B为2000/秒,服务C为1w/第二。同时,部分客户端可能拒绝了请求,而部分客户端可以正常运行,限流作为服务端的自我保护能力。常见的限流算法有:计数器、漏桶、令牌桶等,但计数器算法无法实现平滑限流,因此在实际应用中很少使用。7、熔断器自施耐德发明断路器以来,熔断器的概念就风靡全球。从A股熔断器到服务熔断器,有很多相似之处。熔断器是指:当电路闭合时,电流可以流动,当断路器分闸时,电流停止。通常,一个用户请求需要多个后端服务的配合才能完成工作。并非所有这些后端服务都是必需的。因为其中一项服务有问题而拒绝用户的整个请求,那是非常不合理的。断路器期望某些服务在出现问题时返回一些默认值。整个请求还是可以正常进行的。比如风控。如果某个时间段风控服务不可用,用户应该可以正常交易。这时候我们应该默认风控通过,然后将这些异常交易转储到其他地方,待风控恢复后再出货前尽快处理。从上面的描述可以看出,有些服务在熔断后只是简单的返回一些默认的数据,比如推荐服务;但是有些服务需要有相应的异常处理支持,就是一个ifelse;更何况有些业务不支持熔断器,那只能是FailFast。单脑处理是一种不用思考的技术手段,不是我们推荐的。Hystrix、resilience4j、Sentinel等组件是Java系统中广泛使用的工具。通过SpringBoot的集成,这些框架一般使用起来更加方便,可以实现配置编程。8.降级降级是一个模糊的术语。限流熔断在一定程度上也可以看作是一种退化。但通常所说的降级,是指切入级别更高级。降级一般考虑分布式系统的完整性,从源头上切断流量来源。比如双11期间,为了保证交易系统,会暂停一些不重要的服务,避免资源争用。服务降级是人工参与,人为造成部分服务不可用,多为业务降级方式。降级的最佳位置在哪里?这是入口。比如Nginx,比如DNS等。在一些互联网应用中,会有MVP(MinimumViableProduct)的概念,也就是最小可行产品,它的SLA要求非常高。围绕最小可行产品,会有一系列的服务拆分操作,当然有些情况甚至需要重写。例如,一个电子商务系统,在极端情况下,只需要展示产品并进行销售。其他一些支持系统,如评论、推荐等,可以暂时关闭。在物理部署和调用关系上,必须考虑这些情况。9、预热请看以下情况。高并发环境下的DB在进程挂掉后重启。由于业务高峰期,对上游负载均衡策略进行了重新分配。刚启动的DB瞬间收到了1/3的流量,然后负载疯狂飙升,直到没有任何响应。原因是:对于新启动的DB,各种??缓存还没有准备好,系统状态和正常运行时完全不一样。大概平时用量的1/10就可以把它弄死。同样,对于一个刚启动的JVM进程,由于字节码没有经过JIT编译器的优化,刚启动时所有接口的响应时间都比较慢。如果不考虑新启动的情况,调用它的负载均衡组件,1/n的流量正常路由到这个节点,容易出问题。因此,我们希望负载均衡组件能够根据JVM进程的启动时间动态缓慢增加量,对服务进行预热,直到达到正常的流量水平。10.背压考虑以下两种情况:没有电流限制。如果请求量过大,想充多少就充多少,容易造成后端服务崩溃或者内存溢出传统限流。你强行规定了一个接口的最大承载能力,超过了直接拒绝,但是此时后端服务是有能力处理这些请求的。如何动态修改限流值?这就需要一套机制。主叫方需要知道被叫方的处理能力,即被叫方需要有反馈的能力。背压,英文BackPressure,其实是一种智能限流,指的是一种策略。采用反压思想,被请求方不会直接丢弃请求端的流量,而是不断反馈自己的处理能力。请求者根据这些反馈实时调整发送频率。一个典型的场景是在TCP/IP中使用滑动窗口进行流量控制。反应式编程(Reactive)是观察者模式的集大成者。大多采用事件驱动、非阻塞的弹性应用,基于数据流的弹性传输。在这种情况下,背压实现要简单得多。背压使系统更稳定,利用率更高。它具有更高的弹性和智能。比如我们常见的HTTP429状态终端,就是请求太多了,让客户端休息一下,不要那么着急。这是一个智能通知。11.隔离即使在同一个实例中,有时也必须隔离同类型的资源。一个比较简单的类比是泰坦尼克号,它有多个舱室。各舱室相互隔离,防止单个舱室进水导致整船沉没。当然,泰坦尼克号是带着愤怒的杰克沉没的,那是因为船舱破损太大了。在一些公司的软件中,报表查询服务、定时任务、常用服务都放在同一个tomcat中。它们使用同一组数据库连接池。当某些报表接口的请求增加时,其他正常的服务将无法使用。这是混合资源的结果。除了遵循CQRS来拆分服务之外,一个快速的机制是隔离某些类型服务所使用的资源。例如,为报表分配一个单独的数据库连接池和一个单独的限流器,不会影响其他服务。耦合不仅发生在无状态服务节点上,也发生在存储节点上。与其将报表服务的存储和正常业务的存储放在一个数据库中,不如将它们分开,分别提供服务。一个和尚挑水喝,两个和尚也可以挑水喝。原因是他们在两个寺庙里。12.异步如果你比较过BIO和NIO的区别,你会发现我们的服务其实大部分时间都在等待返回,CPU根本就没有满负荷运行。当然,NIO是底层机制,避免了线程膨胀和频繁的上下文切换。服务的异步化有点类似于NIO,采用后可以避免不必要的等待。尤其是当调用路径较长时,异步不会阻塞,响应会变快。在单机环境下,我们会使用NIO;在分布式环境中,我们将使用MQ。虽然它们是不同的技术,但原理是相同的。异步通常涉及编程模型的更改。在同步模式下,请求将被阻塞,直到有成功或返回失败结果。其编程模型虽然简单,但在处理时隙偏斜的突发流量时问题尤为突出,请求容易失败。异步操作可以平滑的横向扩容,也可以上下移动瞬时压力时间。同步请求就像拳头打钢板;异步请求就像拳头打海绵。这个过程大家可以想象一下,后者肯定更灵活,体验更友好。13.缓存缓存可能是软件中最常用的优化技术。比如在最核心的CPU中,有一个多级缓存;为了消除内存和存储的区别,各种类似Redis的缓存框架层出不穷。缓存的优化效果很好。可以让加载很慢的页面瞬间秒级打开;还可以让压力很大的数据库瞬间闲置。缓存本质上是协调两个速度差异很大的组件。通过增加一个中间层,将常用数据存储在相对高速的设备中。在应用开发中,缓存分为本地缓存和分布式缓存。那么什么是分布式缓存呢?它实际上是一种集中管理的思想。如果我们的服务有多个节点,那么每个节点上都会有一份堆内缓存的副本;而分布式缓存,所有节点共享一个缓存,既节省了空间,又降低了管理成本。在分布式缓存领域,Redis是用的最多的。Redis支持的数据类型非常丰富,包括字符串(string)、列表(list)、集合(set)、有序集合(zset)、哈希表(hash)等常见的数据结构。当然,它也支持位图等其他一些数据结构。因此,新增的问题必须集中在缓存穿透、击穿、雪崩、一致性等方面。我不会谈论这个。14.Plan-B一个成熟的系统都有一个PlanB,除了异地多活和容灾解决方案,Plan-B也意味着我们需要为正常的服务提供异常通道。例如,致力于运行最小可行系统来运行公司的核心业务。如果发生大规模故障,请求将完全切换到这个最小系统。Plan-B通常是全球性的。它保证了公司最基本的服务能力,我们希望它永远不会被使用。15、监控报警问题是有问题的,因为它留下了证据。没有证据,虽然可以看到效果,但是找不到罪魁祸首。而且这个问题通常是人为的,当它发现找不到的时候,它总是会再次出现。就好像犯罪分子发现了一个漏洞并试图再次利用它。所以,为了处理线上问题,需要留下问题发生的证据,这是最重要的。没有这些东西,你的公司肯定会陷入无休止的扯皮。日志记录是最常见的做法。通过在程序逻辑中打点,配合Logback等日志框架,可以快速定位到出现问题的代码行。我们需要查看bug的详细发生过程,详细记录可能出现问题的逻辑,进行更详细的日志输出。当出现问题时,我们可以切换到debug进行调试。如果是大规模bug,强烈建议直接在线调试。不推荐使用Arthas等工具动态修改字节码进行测试,当然也不推荐使用IDEA的远程调试。相反,建议采用类似金丝雀发布的方式,导出极小部分流量,构建新版本进行测试。如果你没有金丝雀发布平台,像Nginx这样的负载均衡工具也可以通过权重来做类似的事情。日志系统和监控系统对硬件的要求比较大,尤其是当你的请求体和返回体比较大的时候,对存储和计算资源的要求就更高了。其硬件成本在整个基础设施中所占比例较高。但是这样的证据信息对于分析问题是非常必要的。所以即使再贵一些,很多企业还是会有很多的投入在上面,包括硬件投入和人力投入。MTTD和MTTR是两个非常重要的指标,我们一定要多加关注。16.最后看了看表,这家伙还挺会说话的,预定的时间很快就过去了。我摆摆手停下:“你还有什么事?简单说一下!”实际故障排除。还有操作系统怎么优化,网络编程,多线程,这些我还没讲呢。”我说,“行了,你已经很厉害了。”压垮了大多数人。你是哪里的毕业生?”“我是B站的,昨天刚毕业~”他害羞的笑着。我看着他的眼睛也笑了又来了两次!太棒了。
