基础篇中,我们了解了高并发系统设计三个通用的方法,缓存、异步、横向扩展。目前你接触了缓存的使用,也了解了如何使用消息队列异步处理业务逻辑。那么本章我们来看下如何增强系统横向扩展能力。 实际工作中,你经常使用的负载均衡组件应该Nginx,他的作用应该是承接前段http请求,然后按照设计的既定策略转发到后端对应的服务上去。这样我们可以随时扩容业务服务来抵挡流量峰值。与DNS不同,Nginx可以在域名和请求URL上做更细致的流量管控,也提供更复杂的负载均衡策略。 那么在微服务架构中,如何使用负载均衡呢?负载均衡服务器的种类 负载均衡的含义:将负载(访问的请求)均衡的分配到多个节点上,这样可以减少单个节点的请求,提高系统的性能。 同时,负载均衡作为流量的入口,可以对请求方屏蔽后端服务的细节,实现对业务方的无感知扩容。 负载均衡大致可以分为两位:代理类的负载均衡和客户端负载均衡 代理类负载均衡以服务的方式单独部署,所有请求都要经过负载均衡服务,在负载均衡选出一个合适的服务节点后,再调用这个服务节点来实现服务的分发。 由于这类服务需要承担极高的流量,所以对于性能要求极高,代理类负载均衡有许多开源实现,比如LVS、Nginx等等。LVS在OSI网络模型中的第四层,传输层工作,所以LVS又可以称为四层负载;而Nginx运行在OSI网络模型中的第七层,应用层,所以又可以称它为七层负载(你可以回顾一下02讲的内容)。 在项目的架构中,我们一般会同时部署LVS和Nginx来做HTTP应用服务的负载均衡。也就是说,在入口处部署LVS将流量分发到多个Nginx服务器上,再由Nginx服务器分发到应用服务器上,为什么这么做呢? 主要LVS和Nginx的特点决定,LVS在网络栈的四层做请求包的转发,请求包转发之后,客户端和后端服务直接建立链接,后续的响应包不会再经过LVS服务器,所以相比Nginx性能会更高,也能够承担更高的并发。 可LVS缺陷是工作在四层,而请求的URL是七层的概念,不能针对URL做更细致的请求分发,而且LVS也没有提供探测后端服务是否存活的机制;而Nginx虽然比LVS的性能差很多,但也可以承担每秒几万次的请求,并且它在配置上更加灵活,还可以感知后端服务是否出现问题。 因此,LVS适合在入口处承担大流量的请求分发,而Nginx要部署在业务服务器之前做更细维度的请求分发。我给你的建议是,如果你的QPS在十万以内,那么可以考虑不引入LVS而直接使用Nginx作为唯一的负载均衡服务器,这样少维护一个组件,也会减少系统的维护成本。 不过这两个负载均衡服务适用于普通的Web服务,对于微服务架构来说,它们是不合适的。因为微服务架构中的服务节点存储在注册中心里,使用LVS就很难和注册中心交互获取全量的服务节点列表。另外,一般微服务架构中,使用的是RPC协议而不是HTTP协议,所以Nginx也不能满足要求。 所以,我们会使用另一类的负载均衡服务,客户端负载均衡服务,也就是把负载均衡的服务内嵌在RPC客户端中。 它一般和客户端应用部署在一个进程中,提供多种选择节点的策略,最终为客户端应用提供一个最佳的、可用的服务端节点。这类服务一般会结合注册中心来使用,注册中心提供服务节点的完整列表,客户端拿到列表之后使用负载均衡服务的策略选取一个合适的节点,然后将请求发到这个节点上。 常见的负载均衡策略有哪些一类是静态策略,也即是说负载均衡在选择后端服务节点时,是不考虑后端服务的存活状态的。一类动态策略,也就是说负载均衡会依照后端服务存活状态,来选择哪一个服务的转发 常见的静态策略有几种,使用最广泛的轮询策略,RoundRobin,RR,这种策略会记录上次请求的服务地址序号,下次请求过来,顺次选择下一个服务节点。AtomicIntegerlastCountergetLastCounter();获取上次请求的服务节点的序号ListStringserverListgetServerList();获取服务列表intcurrentIndexlastCounter。addAndGet();增加序列号if(currentIndexserverList。size()){currentIndex0;}setLastCounter(currentIndex);returnserverList。get(currentIndex); 其实是一种通用策略,大多数负载均衡服务器都支持。但是这种策略一个弊端就是不考虑后端服务器的配置,因为后面服务的机器配置不可能都是一致的,这样导致每台服务节点承受的能力也不同,这点是轮询没有考虑到的。 所以就出现了一种这种策略就是带有权重的轮询策略。以应对后端服务不同的承载能力。 除了这两种策略之外,目前开源的负载均衡服务还提供了很多静态策略: Nginx提供了iphash和urlhash算法;LVS提供了按照请求的源地址和目的地址做Hash的策略;Dubbo也提供了随机选取策略以及一致性Hash的策略。 而目前开源的负载均衡服务中,也会提供一些动态策略,我强调一下它们的原理。 在负载均衡服务器上会收集对后端服务的调用信息,比如从负载均衡端到后端服务的活跃连接数,或者是调用的响应时间,然后从中选择连接数最少的服务,或者响应时间最短的后端服务。我举几个具体的例子:Dubbo提供的LeastAcive策略,就是优先选择活跃连接数最少的服务;SpringCloud全家桶中的Ribbon提供了WeightedResponseTimeRule是使用响应时间给每个服务节点计算一个权重,然后依据这个权重,来给调用方分配服务节点。 这些策略的思考点是从调用方的角度出发,选择负载最小、资源最空闲的服务来调用,以期望能得到更高的服务调用性能,也就能最大化地使用服务器的空闲资源,请求也会响应得更迅速。所以我建议你,在实际开发中,优先考虑使用动态的策略。 到目前为止,你已经可以根据上面的分析,选择适合自己的负载均衡策略,并选择一个最优的服务节点。那么问题来了:你怎么保证选择出来的这个节点,一定是一个可以正常服务的节点呢?如果你采用的是轮询的策略,选择出来的是一个故障节点又要怎么办呢?所以,为了降低请求被分配到一个故障节点的几率,有些负载均衡服务器还提供了对服务节点的故障检测功能。如何检测节点是否故障 微服务化架构中,服务节点会定期地向注册中心发送心跳包,这样注册中心就能够知晓服务节点是否故障,也就可以确认传递给负载均衡服务的节点一定是可用的。 但对于Nginx来说,我们要如何保证配置的服务节点是可用的呢? 这就要感谢淘宝开源的Nginx模块nginxupstreamcheckmodule了,这个模块可以让Nginx定期地探测后端服务的一个指定的接口,然后根据返回的状态码来判断服务是否还存活。当探测不存活的次数达到一定阈值时,就自动将这个后端服务从负载均衡服务器中摘除。它的配置样例如下:upstreamserver{server192。168。1。1:8080;server192。168。1。2:8080;checkinterval3000rise2fall5timeout1000typehttpdefaultdowntrue;检测间隔为3秒,检测超时时间是1秒,使用http协议。如果连续失败次数达到5次就认为服务不可用;如果连续成功次数达到2次,则认为服务可用。后端服务刚启动时状态是不可用的checkhttpsendGEThealthcheckHTTP1。0rr;检测URLcheckhttpexpectalivehttp2xx;检测返回状态码为200时认为检测成功} Nginx按照上面的方式配置之后,你的业务服务器也要实现一个healthcheck的接口,在这个接口中返回的HTTP状态码,这个返回的状态码可以存储在配置中心中,这样在变更状态码时,就不需要重启服务了 节点检测的功能,还能够帮助我们实现Web服务的优雅关闭。在24讲中介绍注册中心时,我曾经提到,服务的优雅关闭需要先切除流量再关闭服务,使用了注册中心之后,就可以先从注册中心中摘除节点,再重启服务,以便达到优雅关闭的目的。那么Web服务要如何实现优雅关闭呢?接下来,我们了解一下有了节点检测功能之后,服务是如何启动和关闭的。 在服务刚刚启动时,可以初始化默认的HTTP状态码是500,这样Nginx就不会很快将这个服务节点标记为可用,也就可以等待服务中依赖的资源初始化完成,避免服务初始启动时的波动。 在完全初始化之后,再将HTTP状态码变更为200,Nginx经过两次探测后,就会标记服务为可用。在服务关闭时,也应该先将HTTP状态码变更为500,等待Nginx探测将服务标记为不可用后,前端的流量也就不会继续发往这个服务节点。在等待服务正在处理的请求全部处理完毕之后,再对服务做重启,可以避免直接重启导致正在处理的请求失败的问题。这是启动和关闭线上Web服务时的标准姿势,你可以在项目中参考使用。