ribbon:在其他功能区模块和Hystrix之上集成负载平衡,容错,缓存/批处理的API
ribbon-core:主要定义通用的调用抽象,ribbon的client处理请求并返回响应
ribbon-loadbalancer:负载均衡,宏观上效果是将请求流量均衡的负载到提供服务器上,对于每次请求通过计算获取目标服务器地址
ribbon-httpclient:负载均衡器集成的Apache HttpClient之上的REST客户端
ribbon-eureka:使用Eureka客户端为注册中心提供动态服务器列表的API
LoadBalancerAutoConfiguration
// ......
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate :
LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
}
};
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
// 拦截器配置
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
}
// ......
Ribbon实现负载均衡自动化配置条件
@ConditionalOnClass(RestTemplate.class): RestTemplate类必须存在于当前工程的环境中
@ConditionalOnBean(LoadBalancerClient.class): 在Spring的Bean工厂中需要有LoadBalancerClient
自动化配置流程
创建一个LoadBalancerInterceptor用于实现对客户端的发起请求时进行拦截,以实现客户端负载均衡
创建一个RestTemplateCustomizer用于给RestTemplate增加LoadBalancerInterceptor拦截器
维护一个被@LoadBalanced注解修饰的RestTemplate对象列表并初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor
LoadBalancerInterceptor 负载均衡拦截器
当一个被@LoadBalanced注解修饰的RestTemplate对象发起一个HTTP请求的时候,会被LoadBalancerInterceptor拦截执行intercept方法,通过HttpRequest的URI对象中通过getHost获取服务名,然后调用execute方法根据服务名选择实例发起请求。
@Override
public ClientHttpResponse intercept(final HttpRequest request,
final byte[] body,
final ClientHttpRequestExecution execution)
throws IOException {
final URI originalUri = request.getURI();
// 获取到服务名
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "......");
// 选择服务实例发起请求
return this.loadBalancer.execute(serviceName,
requestFactory.createRequest(request, body, execution));
}
LoadBalancerClient只是一个接口,Ribbon提供了它的一个实现类RibbonLoadBalancerClient
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
// 根据服务Id获取负载均衡器
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 根据负载均衡器获取服务
Server server = getServer(loadBalancer);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
每个服务都会拥有一个SpringClientFactory用于创建负载均衡器ILoadBalancer的实现类,ILoadBalancer可以更具所有服务实例选择某一个服务。
实现负载均衡逻辑的接口ILoadBalancer,其实现默认实现类为ZoneAwareLoadBalancer
public interface ILoadBalancer {
//向负载均衡器中添加服务实例
void addServers(List<Server> var1);
//通过负载均衡策略从中选择一个服务实例
Server chooseServer(Object var1);
//标记一个服务的停止
void markServerDown(Server var1);
@Deprecated
List<Server> getServerList(boolean var1);
// 获取可用服务实例
List<Server> getReachableServers();
// 获取所有服务实例
List<Server> getAllServers();
}
ZoneAwareLoadBalancer
public Server chooseServer(Object key) {
// ......
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(
zoneSnapshot, this.triggeringLoad.get(),
this.triggeringBlackoutPercentage.get());
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size())
{
// 会将服务分成几个区域
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
if (zone != null) {
BaseLoadBalancer zoneLoadBalancer = this.getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);
}
if (server != null) {
return server;
} else {
return super.chooseServer(key);
}
} else {
return super.chooseServer(key);
}
}
IRule是负载均衡策略的一个接口,默认使用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;
}
// 获取实例在List中的下标
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;
}
// 尝试10次
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
Ribbon支持根据不同的服务配置不同的负载均衡策略
配置类
@Configuration
@ExcludeFromComponentScan
public class TestConfiguration1 {
@Autowired
private IClientConfig config;
@Bean
public IRule ribbonRule(IClientConfig config) { // 自定义为随机规则
return new RandomRule();
}
}
microservice-provider-user: # 服务名
ribbon:
# 自定义负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
# 脱离Eureka需要提供服务地址
listOfServers:localhost:8080,localhost:8081
选择完服务实例后执行apply访问服务,传入ServiceInstance
提供服务实例需要的信息
public interface ServiceInstance {
String getServiceId();
String getHost();
int getPort();
boolean isSecure();
URI getUri();
Map<String, String> getMetadata();
}
将HttpRequest包装成ServiceRequestWrapper
public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
final byte[] body, final ClientHttpRequestExecution execution) {
return new LoadBalancerRequest<ClientHttpResponse>() {
@Override
public ClientHttpResponse apply(final ServiceInstance instance)
throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
loadBalancer);
if (transformers != null) {
for (LoadBalancerRequestTransformer transformer : transformers) {
serviceRequest = transformer.transformRequest(serviceRequest, instance);
}
}
return execution.execute(serviceRequest, body);
}
};
}
// InterceptingClientHttpRequest,InterceptingRequestExecution 内部类
@Override
public ClientHttpResponse execute(HttpRequest request, final byte[] body)
throws IOException {
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);
}
else {
// 会调用RibbonHttpRequest重写的getURI()
ClientHttpRequest delegate = requestFactory.
createRequest(request.getURI(), request.getMethod());
for (Map.Entry<String, List<String>> entry :
request.getHeaders().entrySet()) {
List<String> values = entry.getValue();
for (String value : values) {
delegate.getHeaders().add(entry.getKey(), value);
}
}
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage =
(StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(
new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(final OutputStream outputStream)
throws IOException {
StreamUtils.copy(body, outputStream);
}
});
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
return delegate.execute();
}
}
最后经过一系列的包装会在SimpleBufferingClientHttpRrequest#executeInternal中完成访问
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
addHeaders(this.connection, headers);
// JDK <1.8 doesn't support getOutputStream with HTTP DELETE
if (getMethod() == HttpMethod.DELETE && bufferedOutput.length == 0) {
this.connection.setDoOutput(false);
}
if (this.connection.getDoOutput() && this.outputStreaming) {
this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
}
// 连接服务
this.connection.connect();
if (this.connection.getDoOutput()) {
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
}
else {
// Immediately trigger the request in a no-output scenario as well
this.connection.getResponseCode();
}
return new SimpleClientHttpResponse(this.connection);
}
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
dependency>
传递依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-netflix-coreartifactId>
dependency>
<dependency>
<groupId>com.netflix.ribbongroupId>
<artifactId>ribbonartifactId>
dependency>
<dependency>
<groupId>com.netflix.ribbongroupId>
<artifactId>ribbon-coreartifactId>
dependency>
<dependency>
<groupId>com.netflix.ribbongroupId>
<artifactId>ribbon-httpclientartifactId>
dependency>
<dependency>
<groupId>com.netflix.ribbongroupId>
<artifactId>ribbon-loadbalancerartifactId>
dependency>
这个和普通的微服务没有区别,也属于Eureka的客户端
spring.application.name=service-ribbon
server.port=8763
eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
feign.httpclient.connection-timeout=50000
# 调试超时被熔断使用
# hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
最为主要的区别就是在RestTemplate加上了负载均衡@LoadBalanced支持
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRibbonApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
其实RestTemplate是Spring Web中就已经提供了,并不是ribbon中的类,是用@LoadBalanced注解通过负载均衡客户端LoadBalancerClient来配置的。
主要流程:
首先是要申明一个负载均衡类RestTemplate,它主要是通过我们的在配置文件中配置路径/服务映射关系(默认为服务名对应的Eureka中心的地址),在发起服务请求的时候拦截该请求(通过注入LoadBalancerInterceptor),然后根据服务编号获取服务主机等信息,然后根据负载均衡策略获取具体的服务所在host、port,在此期间会进行服务的获取,判断可用性,服务更新,重试机制等,最后重新封装HTTP请求,最后执行包装HTTP请求返回的结果。