第一种是集中式LB方案,如下图,在服务消费者和服务提供者之间有一个独立的LB,LB通常是专门的硬件设备如F5,或者基于软件如LVS,HAproxy等实现。LB上有所有服务的地址映射表,通常由运维配置注册,当服务消费方调用某个目标服务时,它向LB发起请求,由LB以某种策略(比如Round-Robin)做负载均衡后将请求转发到目标服务。LB一般具备健康检查能力,能自动摘除不健康的服务实例。服务消费方如何发现LB呢?通常的做法是通过DNS,运维人员为服务配置一个DNS域名,这个域名指向LB。
集中式LB方案实现简单,在LB上也容易做集中式的访问控制,这一方案目前还是业界主流。集中式LB的主要问题是单点问题,所有服务调用流量都经过LB,当服务数量和调用量大的时候,LB容易成为瓶颈,且一旦LB发生故障对整个系统的影响是灾难性的。另外,LB在服务消费方和服务提供方之间增加了一跳(hop),有一定性能开销。
第二种是进程内LB方案,针对集中式LB的不足,进程内LB方案将LB的功能以库的形式集成到服务消费方进程里头,该方案也被称为软负载(Soft Load Balancing)或者客户端负载方案,下图Fig 2展示了这种方案的工作原理。这一方案需要一个服务注册表(Service Registry)配合支持服务自注册和自发现,服务提供方启动时,首先将服务地址注册到服务注册表(同时定期报心跳到服务注册表以表明服务的存活状态,相当于健康检查),服务消费方要访问某个服务时,它通过内置的LB组件向服务注册表查询(同时缓存并定期刷新)目标服务地址列表,然后以某种负载均衡策略选择一个目标服务地址,最后向目标服务发起请求。这一方案对服务注册表的可用性(Availability)要求很高,一般采用能满足高可用分布式一致的组件(例如Zookeeper, Consul, Etcd等)来实现。
进程内LB方案是一种分布式方案,LB和服务发现能力被分散到每一个服务消费者的进程内部,同时服务消费方和服务提供方之间是直接调用,没有额外开销,性能比较好。但是,该方案以客户库(Client Library)的方式集成到服务调用方进程里头,如果企业内有多种不同的语言栈,就要配合开发多种不同的客户端,有一定的研发和维护成本。另外,一旦客户端跟随服务调用方发布到生产环境中,后续如果要对客户库进行升级,势必要求服务调用方修改代码并重新发布,所以该方案的升级推广有不小的阻力。
进程内LB的案例是Netflix的开源服务框架,对应的组件分别是:Eureka服务注册表,Karyon服务端框架支持服务自注册和健康检查,Ribbon客户端框架支持服务自发现和软路由。另外,阿里开源的服务框架Dubbo也是采用类似机制。teld的分布式服务框架就是采用此种方案。
第三种是主机独立LB进程方案,该方案是针对第二种方案的不足而提出的一种折中方案,原理和第二种方案基本类似,不同之处是,他将LB和服务发现功能从进程内移出来,变成主机上的一个独立进程,主机上的一个或者多个服务要访问目标服务时,他们都通过同一主机上的独立LB进程做服务发现和负载均衡,见下图Fig 3。
该方案也是一种分布式方案,没有单点问题,一个LB进程挂了只影响该主机上的服务调用方,服务调用方和LB之间是进程内调用,性能好,同时,该方案还简化了服务调用方,不需要为不同语言开发客户库,LB的升级不需要服务调用方改代码。该方案的不足是部署较复杂,环节多,出错调试排查问题不方便。
该方案的典型案例是Airbnb的SmartStack服务发现框架,对应组件分别是:Zookeeper作为服务注册表,Nerve独立进程负责服务注册和健康检查,Synapse/HAproxy独立进程负责服务发现和负载均衡。Google最新推出的基于容器的PaaS平台Kubernetes,其内部服务发现采用类似的机制。