Spring Cloud Ribbon是基于Netflix Ribbon 实现的一套客户端的负载均衡工具,Ribbon 客户端组件提供一系列的完善的配置,如超时、重试等。通过Load Balancer(LB)获取到服务提供的所有机器实例,Ribbon会自动基于某种规则(轮询、随机等)去调用这些服务。 Ribbon也可以实现我们自己的负载均衡算法。
客户端负载均衡:就是进程内的LB,他是一个类库集成到消费端,通过消费端进行获取提供者的地址。
服务端负载均衡:请求调用负载均衡器,由负载均衡器进行算法解析决定调用哪一个服务。
首先Eureka Client 会从 Eureka Server 拉取服务列表缓存到本地,然后Ribbon 会从 Eureka Client 的本地缓存中复制一份到自己的缓存区。然后通过 LoadBalancerInterceptor 拦截器会将服务名替换为真实的 IP:PORT ,再通过LoadBalancerClient 使用Http 方式进行接口调用。
2.1 Ribbon 快速集成
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
添加@LoadBalanced注解
@Configuration
public class AppConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2.2 Ribbon负载均衡策略
RandomRule: 随机选择一个Server。
RetryRule: 对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择 Server不成功,则一直尝试使用subRule的方式选择一个可用的server。
RoundRobinRule: 轮询选择, 轮询index,选择index对应位置的Server。
AvailabilityFilteringRule: 过滤掉一直连接失败的被标记为circuit tripped的 后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate 来包含过滤server的逻辑,其实就是检查status里记录的各个Server的运行状态。
BestAvailableRule: 选择一个最小的并发请求的Server,逐个考察Server,如 果Server被tripped了,则跳过。
WeightedResponseTimeRule: 根据响应时间加权,响应时间越长,权重越 小,被选中的可能性越低。
ZoneAvoidanceRule: 复合判断Server所在区域的性能和Server的可用性选择 Server。
2.3 修改默认负载均衡策略
方式一:自定义@Bean
@Configuration
public class AppConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public IRule myRule() {
return new RandomRule();
}
}
方式二:添加配置
# 使用.ribbon.=的形式进行配置
# 参考org.springframework.cloud.netflix.ribbon.PropertiesFactory
service-order.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
2.4 自定义均衡算法
可以通过实现 IRule 接口或继承 AbstractLoadBalancerRule 抽象类的方式自定义负载均衡算法。
示例:
public class MyRandomRule extends AbstractLoadBalancerRule {
/** 总共被调用的次数,目前要求每台被调用5次 */
private int total = 0;
/** 当前提供服务的机器号 */
private int currentIndex = 0;
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
//激活可用的服务
List upList = lb.getReachableServers();
//所有的服务
List allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
if (total < 5) {
server = upList.get(currentIndex);
total++;
} else {
total = 0;
currentIndex++;
if (currentIndex >= upList.size()) {
currentIndex = 0;
}
}
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
在SpringBoot主程序扫描的包外定义配置类。自定义的负载均衡策略不能写在@SpringbootApplication注解的@CompentScan 扫描得到的地方,否则自定义的配置类就会被所有的 RibbonClients共享。
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new MyRandomRule();
}
}
使用@RibbonClient指定负载均衡策略,在SpringBoot主程序添加 @RibbonClient 引入配置类:
@SpringBootApplication
@RibbonClient(name = "service-order",configuration = MySelfRule.class)
public class ServiceMemberApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceMemberApplication.class, args);
}
}
2.5 Ribbon 相关接口作用
参考: org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration 。
IClientConfig:Ribbon的客户端配置,默认采用DefaultClientConfigImpl实现。
IRule:Ribbon的负载均衡策略,默认采用ZoneAvoidanceRule实现,该策略能够在多区 域环境下选出最佳区域的实例进行访问。
IPing:Ribbon的实例检查策略,默认采用DummyPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认认为所有服务实例都是可用的。
ServerList:服务实例清单的维护机制,默认采用ConfigurationBasedServerList实现。
ServerListFilter:服务实例清单过滤机制,默认采用ZonePreferenceServerListFilter,该策略能够优先过滤出与请求方处于同区域的服务实例。
ILoadBalancer:负载均衡器,默认采用ZoneAwareLoadBalancer实现,它具备区域感知的能力。
入口:org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
@Autowired(required = false)
private List configurations = new ArrayList<>();
@Autowired
private RibbonEagerLoadProperties ribbonEagerLoadProperties;
@Bean
public HasFeatures ribbonFeature() {
return HasFeatures.namedFeature("Ribbon", Ribbon.class);
}
// Spring 工厂,拿到容器里所有的@configurations
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
// 初始化 LoadBalancerClient ,最终会执行LoadBalancerClient的execute方法执行远程调用逻辑
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
@Bean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(
final SpringClientFactory clientFactory) {
return new RibbonLoadBalancedRetryFactory(clientFactory);
}
@Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
return new PropertiesFactory();
}
@Bean
@ConditionalOnProperty("ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
return new RibbonApplicationContextInitializer(springClientFactory(),
ribbonEagerLoadProperties.getClients());
}
@Configuration
@ConditionalOnClass(HttpRequest.class)
@ConditionalOnRibbonRestClient
protected static class RibbonClientHttpRequestFactoryConfiguration {
@Autowired
private SpringClientFactory springClientFactory;
@Bean
public RestTemplateCustomizer restTemplateCustomizer(
final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
return restTemplate -> restTemplate
.setRequestFactory(ribbonClientHttpRequestFactory);
}
@Bean
public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
return new RibbonClientHttpRequestFactory(this.springClientFactory);
}
}
// TODO: support for autoconfiguring restemplate to use apache http client or okhttp
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnRibbonRestClientCondition.class)
@interface ConditionalOnRibbonRestClient {
}
private static class OnRibbonRestClientCondition extends AnyNestedCondition {
OnRibbonRestClientCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@Deprecated // remove in Edgware"
@ConditionalOnProperty("ribbon.http.client.enabled")
static class ZuulProperty {
}
@ConditionalOnProperty("ribbon.restclient.enabled")
static class RibbonProperty {
}
}
/**
* {@link AllNestedConditions} that checks that either multiple classes are present.
*/
static class RibbonClassesConditions extends AllNestedConditions {
RibbonClassesConditions() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnClass(IClient.class)
static class IClientPresent {
}
@ConditionalOnClass(RestTemplate.class)
static class RestTemplatePresent {
}
@ConditionalOnClass(AsyncRestTemplate.class)
static class AsyncRestTemplatePresent {
}
@ConditionalOnClass(Ribbon.class)
static class RibbonPresent {
}
}
}
@RibbonClients 解析
@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({RibbonClientConfigurationRegistrar.class})
public @interface RibbonClients {
RibbonClient[] value() default {};
Class>[] defaultConfiguration() default {};
}
@Import({RibbonClientConfigurationRegistrar.class})
// 注册 RibbonClient,前面自定义负载均衡算法时在启动类添加注解 @RibbonClient(name = "service-order",configuration = MySelfRule.class),在此生效
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 解析 RibbonClients 注解
Map attrs = metadata
.getAnnotationAttributes(RibbonClients.class.getName(), true);
if (attrs != null && attrs.containsKey("value")) {
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
for (AnnotationAttributes client : clients) {
// 获取 configuration
registerClientConfiguration(registry, getClientName(client),
client.get("configuration"));
}
}
if (attrs != null && attrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
attrs.get("defaultConfiguration"));
}
Map client = metadata
.getAnnotationAttributes(RibbonClient.class.getName(), true);
String name = getClientName(client);
if (name != null) {
registerClientConfiguration(registry, name, client.get("configuration"));
}
}
}
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class)
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List restTemplates = Collections.emptyList();
@Autowired(required = false)
private List transformers = Collections.emptyList();
// 用RestTemplateCustomizer定制所有的restTemplate,即所有被@LoadBalanced注解的restTemplate(匹配给定的限定符注解@Qualifier)
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
/**
* Auto configuration for retry mechanism.
*/
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {
};
}
}
/**
* Auto configuration for retry intercepting mechanism.
*/
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
}
// 定制RestTemplate,将LoadBalancerInterceptor封装到RestTemplate的拦截器集合中
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
当调用服务端接口时,首先经过拦截器org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
// 拦截器入口
@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,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient
public T execute(String serviceId, LoadBalancerRequest request, Object hint)
throws IOException {
// 获取负载均衡器
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 根据负载均衡算法选择一个server
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));
return execute(serviceId, ribbonServer, request);
}