目前主流的负载方案分为以下两种:
集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如 F5),也有软件的(比如 Nginx)。
客户端根据自己的请求情况做负载均衡,Ribbon 就属于客户端自己做负载均衡。
Spring Cloud Ribbon是基于Netflix Ribbon 实现的一套客户端的负载均衡工具,Ribbon客户端组件提供一系列的完善的配置,如超时,重试等。通过Load Balancer获取到服务提供的所有机器实例,Ribbon会自动基于某种规则(轮询,随机)去调用这些服务。Ribbon也可以实现我们自己的负载均衡算法。
Ribbon的原理是代理机制,通过代理实现LoadBalancerClient
统一负载均衡接口,对应的实现类是RibbonLoadBalancerClient
。
除了Ribbon,还可以用其它的负载均衡框架,例如spring-cloud-loadbalancer框架
,对应的实现类是BlockingLoadBalancerClient
,对spring-cloud-loadbalancer框架
的介绍参见 【spring cloud hoxton】Ribbon 真的能被 spring-cloud-loadbalancer 替代吗(负载均衡)
restTemplate作用是发送http请求的客户端
,功能类似httpclient,是spring原生
的框架,用于2个服务之间的请求发送。
与其功能相似的是Spring Cloud Netflix桶的Feign
技术。
package org.springframework.web.client;
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
}
RestTemplate位于spring的web包,是spring web的基本模块。因此,一般通过spring-boot-starter-web来引入。
Nacos是Spring Cloud Alibaba下的服务注册和发现框架,与之类似的技术是Spring Cloud Netflix桶的Eureka。
注意:
Spring Cloud Nacos描述了服务如何进行注册,注册到哪里,服务消费者如何获取服务生产者的服务信息。
当然真正连接的时候,需要Nacos-client.jar、Eureka-client.jar之类的包,这样才能与注册中心进行交互。
Nacos只是维护了服务生产者与注册中心关系
、服务消费者与注册中心关系
,真正的服务消费者调用服务生产者提供的数据是通过RestTemplate实现的。
如果存在多个服务生产者,那么服务消费者调用哪个合适呢?此时需要Spring Cloud Ribbon来解决
所谓负载均衡,就是为资源分配负载,就是选择合适的服务处理请求。
在微服务架构下,SpringCloud提供了统一的客户端负载均衡器的接口LoadBalancerClient
,需要具体的技术来实现该接口,而Ribbon框架提供RibbonLoadBalancerClient
作为实现类。
负载均衡器分为客户端负载均衡器和服务器端负载均衡器(nginx)
我们在微服务架构中,往往通过RestTemplate发送RPC请求,然后通过Ribbon做客户端负载均衡。那么它们是如何配合工作的。
Ribbon 需要访问nacos,获得可用服务列表,spring cloud 提供了统一的查询服务列表的接口类,由nacos提供实现。
很简单,通过 @LoadBalanced标记即可。当然前提是需要引入ribbon的依赖。
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
完整示例参见
前文知道SpringCloud提供了统一的客户端负载均衡器的接口LoadBalancerClient
,而Ribbon使用RibbonLoadBalancerClient
作为实现类:
从上图得知,RibbonLoadBalancerClient
的choose()方法,最终委托Ribbon内部的ILoadBalancer接口实现具体的功能
RibbonLoadBalancerClient
的choose()方法,最终委托Ribbon内部的ILoadBalancer接口实现具体的功能
// ILoadBalancer接口定义了负载均衡器的操作,包括初始化服务列表、选择服务实例、关停服务、获取服务列表等
interface ILoadBalancer {
void addServers(List<Server> newServers);
Server chooseServer(Object key);
void markServerDown(Server server);
List<Server> getReachableServers();
List<Server> getAllServers();
}
ILoadBalancer的直接实现类是抽象类AbstractLoadBalancer
ILoadBalancer的直接实现类是抽象类AbstractLoadBalancer
抽象实现类
,根据服务实例的状态,定义了一个服务分组枚举类ServerGroup,包含三种状态:All,Up,Not_Upabstract class AbstractLoadBalancer implements ILoadBalancer{
public enum ServerGroup {
ALL, STATUS_UP, STATUS_NOT_UP
}
public Server chooseServer() { return chooseServer(null); }
public abstract List<Server> getServerList(ServerGroup serverGroup);
public abstract LoadBalancerStats getLoadBalancerStats();
}
BaseLoadBalancer继承自AbstractLoadBalancer,实现了ILoadBalancer定义的所有方法和AbstractLoadBalancer中的抽象方法。
BaseLoadBalancer是一个完整的负载均衡器的实现类,主要由以下职责:初始化服务实例列表、选择服务实例、关停服务、获取服务实例列表
class BaseLoadBalancer extends AbstractLoadBalancer {
psf IRule DEFAULT_RULE = new RoundRobinRule();
psf SerialPingStrategy DEFAULT_PING_STRATEGY = new SerialPingStrategy();
IRule rule = DEFAULT_RULE; // IRule接口有一个Server choose(Object key)方法,用于选择一个合适的服务实例,BaseLoadBalancer的chooseServer方法就是将工作委托给IRule的choose来完成,默认使用RoundRobinRule——线性负载均衡
IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY; // IPingStrategy接口有一个boolean[] pingServers(IPing, Server[]),定义了ping服务实例的策略,默认使用SerialPingStrategy——使用for循环线性遍历
IPing ping = null; // IPing接口有一个boolean isAlive(Server)方法,用来定义如何去ping一个服务实例,判断其是否处于正常状态
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
volatile List<Server> allServerList = Collections.synchronizedList(new ArrayList<>()); // 全部服务实例
@Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)
volatile List<Server> upServerList = Collections.synchronizedList(new ArrayList<>()); // UP正常状态的服务实例
LoadBalancerStats lbStats; // 存储统计信息
List<ServerListChangeListener> changeListeners = new CopyOnWriteArrayList<>(); // 监听服务变化的监听器,
List<ServerStatusChangeListener> serverStatusListeners = new CopyOnWriteArrayList<>(); // 监听服务实例的状态变化的监听器,markServerDown时回调
// -----------AbstractLoadBalancer接口定义的方法-----------
void addServers(List<Server> newServers) {
ArrayList<Server> newList = new ArrayList<>();
newList.addAll(allServerList); // 将原已维护的服务实例allServerList
newList.addAll(newServers); // 和新传入的服务实例newServers一起,加入到newList中
setServersList(newList); // 然后调用setServersList更新服务实例清单
}
Server chooseServer(Object key) {
return rule.choose(key); // 委托IRule选择服务实例
}
void markServerDown(Server server) {
server.setAlive(false); // 标记服务状态
notifyServerStatusChangeListener(Collections.singleton(server)); // 通知serverStatusListeners中的ServerStatusChangeListener,回调serverStatusChanged方法
}
List<Server> getReachableServers() {
return Collections.unmodifiableList(upServerList); // 返回维护的正常服务实例清单upServerList
}
List<Server> getAllServers() {
return Collections.unmodifiableList(allServerList); // 返回维护的所有服务实例清单allServerList
}
// -----------AbstractLoadBalancer定义的方法-----------
List<Server> getServerList(ServerGroup serverGroup) { // 根据传入的不同分组,返回不同的服务实例清单
ALL -> allServerList;
STATUS_UP -> upServerList;
STATUS_NOT_UP -> allServerList.removeAll(upServerList);
}
LoadBalancerStats getLoadBalancerStats() { // 返回统计信息
return lbStats;
}
// -----------BaseLoadBalancer自身的方法-----------
void setupPingTask() { // 启动ping任务,间隔10s检查allServerList中的Server是否健康,并将状态变化的server通知到serverStatusListeners
// 由BaseLoadBalancer的各构造函数调用,以及ping相关设置变化时调用
lbTimer = new ShutdownEnabledTimer(name="NFLoadBalancer-PingTimer-" + name, daemon=true);
lbTimer.schedule(new PingTask(), delay=0, period=pingIntervalSeconds * 1000);
forceQuickPing();
}
public void setServersList(List lsrv) {
// 比较传入的lsrv中的Server和allServerList中的Server是否相同
// 如果不同,对changeListeners中的每个ServerListChangeListener,回调其serverListChanged方法
// 用lsrv中的Server覆盖allServerList
}
}
LoadBalancerAutoConfiguration
配置类的作用是将所有被@LoadBalanced
注解修饰的RestTemplate bean
经LoadBalancerInterceptor
拦截器进行增强,而LoadBalancerInterceptor又包含loadBalancerClient
,这样当用RestTemplate调用时,会首先调用拦截器方法,在拦截器方法里使用loadBalancerClient
真正实现负载均衡以及url转换,达到服务名到真正的host之间的转换和负载均衡;
Ribbon是使用拦截器来实现服务的远程调用的,源码如下:
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@Bean
// 循环全部增强类去给restTemplate增强,也是在这里给它添加拦截器的
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
//【注入负载均衡器】
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
//【增强RestTemplate】
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
// 注册负载均衡拦截器,LoadBalancerClient 是执行正在服务在均衡的功能类
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
// 注册RestTemplate的增强类
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
// 添加拦截器
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
// 略......
从上面代码可以看到,增强的核心类是LoadBalancerInterceptor
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
进入LoadBalancerInterceptor的intercept()
可以看到,它是使用LoadBalancerClient
去执行真正的流程的,LoadBalancerClient是负责调用流程的。
上述源码中LoadBalancerClient 是一个负载均衡的接口,有不同的负载均衡客户端实现,如果采用Ribbon (或nacos 的spring-cloud-starter-alibaba-nacos-discovery,自带Ribbon )这里的实现是RibbonLoadBalancerClient
:
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
// 【1】从容器中获取负载均衡器
ILoadalancer loadBalancer = getLoadBalancer(serviceId);
// 【2】使用ILoadBalancer获取一个服务
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
// 组装成一个服务实例信息
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
// 【3】调用这个服务
return execute(serviceId, ribbonServer, request);
}
从容器中获取负载均衡器,在RibbonClientConfiguration给容器注册了ZoneAwareLoadBalancer
,所以实际获取的就是它:
接着看一个ZoneAwareLoadBalancer的构建过程做了什么,点进去会来到它的父类的DynamicServerListLoadBalancer
构造方法,源码如下:
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
// 调用上级构造,主要初始化一些配置信息
super(clientConfig, rule, ping);
// serverList服务列表,用于发现服务的
this.serverListImpl = serverList;
// 服务的过滤器
this.filter = filter;
// 服务列表的更新器
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
// 初始化,拉取服务和开启服务更新
restOfInit(clientConfig);
}
核心方法是restOfInit()
初始化,负责拉取服务和开启服务更新
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
// 【1】开启服务列表的监听
enableAndInitLearnNewServersFeature();
// 【2】拉取服务
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
主要看updateListOfServers是如何拉取服务的,方法源码如下:
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
// 调用服务列表服务拉取并更新服务
servers = serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
// 更新内存的服务列表信息
updateAllServerList(servers);
}
变量serverListImpl 是负载均衡的规范接口,类型为ServerList,用于获取服务的,Nacos也是实现的这个接口给Ribbon获取的服务列表的,对应的实现类是NacosServerList:
public interface ServerList<T extends Server> {
public List<T> getInitialListOfServers();
/**
* Return updated list of servers. This is called say every 30 secs
* (configurable) by the Loadbalancer's Ping cycle
*
*/
public List<T> getUpdatedListOfServers();
}
核心抽象方法是getUpdatedListOfServers();
NacosServerList的部分源码:
public class NacosServerList extends AbstractServerList<NacosServer> {
private NacosDiscoveryProperties discoveryProperties;
@Override
public List<NacosServer> getUpdatedListOfServers() {
return getServers();
}
private List<NacosServer> getServers() {
try {
String group = discoveryProperties.getGroup();
// 使用Nacos的NameingService发起Api调用获取服务的实例列表
List<Instance> instances = discoveryProperties.namingServiceInstance()
.selectInstances(serviceId, group, true);
return instancesToServerList(instances);
}
catch (Exception e) {
throw new IllegalStateException(
"Can not get service instances from nacos, serviceId=" + serviceId,
e);
}
}
使用Nacos的NameingService发起Api调用获取服务的实例列表,由NacosDiscoveryProperties 类来实现
到这也就知道服务的发现是怎样的了,剩下的就是根据负载策略选择一个服务去调用了。接着看下一步getServer
重新回到RibbonLoadBalancerClient的第二步getServer,这一步是使用负载均衡器选择一个服务:
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
因为默认的话是不会使用zone的所以会直接进入到BaseLoadBalancer
的chooseServer
方法,源码如下:
public class BaseLoadBalancer {
public Server chooseServer(Object key) {
// 创建计数器或者+1操作
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
// 使用负载策略选择一个服务
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
这一步使用负载策略选择一个服务服务了,至于Rule策略,我会在下一步文章分析,接着看下一步
这一步就是调用服务了:
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException {
Server server = null;
if (serviceInstance instanceof RibbonServer) {
server = ((RibbonServer) serviceInstance).getServer();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
// 发送组装返回结果
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
// catch IOException and rethrow so RestTemplate behaves correctly
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
Ribbon的负载与Nacos的服务发现流程到这就基本分析完了!!
具体的策略配置,待后续补充
事实上,DynamicServerListLoadBalancer是通过父类BaseLoadBalancer注入的负载均衡规则IRule接口的实现类完成负载均衡策略的。该接口的实现类是RoundRobinRule,它实现的策略是轮询规则。(这里是通过拉取server的列表,然后通过索引的原子性操作来完成轮询)
public class RoundRobinRule
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
while (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
SpringCloud客户端负载均衡——Ribbon的LoadBalancer(负载均衡器)
Ribbon原理与Nacos的服务发现原理分析