负载均衡器
Ribbon完成的功能主要是实现客户端负载均衡,那么要完成客户端负载均衡最重要的就是负载均衡器的接口规范定义
package com.netflix.loadbalancer;
public interface ILoadBalancer {
// 添加服务实例
public void addServers(List<Server> newServers);
// 选择访问的服务实例
public Server chooseServer(Object key);
// 标记下线
public void markServerDown(Server server);
// 获得所有可访问的服务实例
public List<Server> getReachableServers();
// 获得所有的服务实例
public List<Server> getAllServers();
}
而对于负载均衡,到底如何负载均衡就需要一些特定的算法,IRule就是定义的接口规范
IRule负载均衡算法
public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none server is available
*/
// 选择服务
public Server choose(Object key);
// 设置负载均衡器
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
服务实例来源
对于负载均衡,如何负载均衡,首先要有服务的来源,其可能是写死在配置文件中的、硬编码在代码中的、或则从注册中心获取的等等,需要定义数据来源
public interface ServerList<T extends Server> {
// 获取
public List<T> getInitialListOfServers();
// 获取更新的服务列表(每隔一定时间[如:30s]更新)
public List<T> getUpdatedListOfServers();
}
服务可用性检查-心跳
既然涉及到服务的可用性更新,那么就需要不断的去检查服务的可用性,一般都是通过Ping维持心跳的方式去检查。
public interface IPing {
/**
* Checks whether the given <code>Server</code> is "alive" i.e. should be
* considered a candidate while loadbalancing
*/
public boolean isAlive(Server server);
}
客户端
我们使用Ribbon的时候正常不直接使用ILoadBalancer
,而是对其做一定的封装,一些定制话的配置,应用中访问了多组服务,每组服务的配置可能略有不同,就需要在其上在做一层封装,达到隔离的效果。
public interface ServiceInstanceChooser {
/**
* Choose a ServiceInstance from the LoadBalancer for the specified service
*/
ServiceInstance choose(String serviceId);
}
public interface LoadBalancerClient extends ServiceInstanceChooser {
// 使用选择的服务实例进行请求
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
// 根据原始配置的url,替换掉服务名为host:port,组装成真正可以访问的URL
URI reconstructURI(ServiceInstance instance, URI original);
}
集成Spring容器及配置
Ribbon一般我们会继承到SpringBoot中使用,将Ribbon的配置配置在配置中心或配置文件中,那么Ribbon要获取Spring环境中的配置信息,就需要持有IOC容器句柄,SpringCloud通过继承容器的方式来持有及访问SpringBoot容器。
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements ApplicationContextAware {
// 每个应用客户端定制配置接口
public interface Specification {
String getName();
Class<?>[] getConfiguration();
}
// 每个服务客户端对应的继承实现
// 如:
// order-service --> IOC Context
// userService --> IOC Context
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
// 各个服务访问客户端定制配置
// order-service --> configurations
// userService --> configurations
private Map<String, C> configurations = new ConcurrentHashMap<>();
// SpringBoot Web容器
private ApplicationContext parent;
// 全局默认配置类
private Class<?> defaultConfigType;
// 属性配置源名称
private final String propertySourceName;
// 属性名
private final String propertyName;
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
// 定制话配置
public void setConfigurations(List<C> configurations) {
for (C client : configurations) {
this.configurations.put(client.getName(), client);
}
}
// 根据服务名获取继承上下文
// 如:getContext("order-service")
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
// 不存在就创建
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
protected AnnotationConfigApplicationContext createContext(String name) {
// 新建IOC容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 将定制配置类加载到容器
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
// 全局默认配置类加载
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
// 属性源
context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object> singletonMap(this.propertyName, name)));
// SpringBoot的web容器
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
}
// 名称
context.setDisplayName(generateDisplayName(name));
// 启动上下文
context.refresh();
return context;
}
protected String generateDisplayName(String name) {
return this.getClass().getSimpleName() + "-" + name;
}
// 从某个客户端容器中获取Bean
// 如:getInstance("order-service", XXX.class)
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) {
return context.getBean(type);
}
return null;
}
}
该类主要目的是替每个Ribbon客户端都创建并缓存一个IOC上下文,这样每个应用客户端就可以单独配置自己的负载均衡器,负载均衡算法。因为这里是先加载定制配置,所以全局配置检查是否已有配置,有了就不添加,因为全局默认配置中用的是**@ConditionalOnMissingBean**
。
其继承实现SpringClientFactory的构造方法较为重要
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
static final String NAMESPACE = "ribbon";
public SpringClientFactory() {
// 默认全局Ribbon配置类
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
// 省略代码....
// 其他代码均是调用父类方法实现
}
这里贴以下Ribbon的默认全局配置类RibbonClientConfiguration
,这个类名中没有Auto,可见不是自动装配范畴。
@Configuration
@EnableConfigurationProperties
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
public static final int DEFAULT_READ_TIMEOUT = 1000;
@RibbonClientName
private String name = "client";
// TODO: maybe re-instate autowired load balancers: identified by name they could be
// associated with ribbon clients
@Autowired
private PropertiesFactory propertiesFactory;
// 配置加载
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
return config;
}
// 负载均衡算法规则实现
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
// 心跳检查
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, config, name);
}
return new DummyPing();
}
// 服务列表封装
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerList.class, name)) {
return this.propertiesFactory.get(ServerList.class, config, name);
}
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
return serverList;
}
// 定时更新服务工具
@Bean
@ConditionalOnMissingBean
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
return new PollingServerListUpdater(config);
}
// 负载均衡器
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
// 过滤规则
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
return this.propertiesFactory.get(ServerListFilter.class, config, name);
}
ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
filter.initWithNiwsConfig(config);
return filter;
}
@Bean
@ConditionalOnMissingBean
public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
IClientConfig config, RetryHandler retryHandler) {
return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
}
@Bean
@ConditionalOnMissingBean
public RetryHandler retryHandler(IClientConfig config) {
return new DefaultLoadBalancerRetryHandler(config);
}
@Bean
@ConditionalOnMissingBean
public ServerIntrospector serverIntrospector() {
return new DefaultServerIntrospector();
}
@PostConstruct
public void preprocess() {
setRibbonProperty(name, DeploymentContextBasedVipAddresses.key(), name);
}
static class OverrideRestClient extends RestClient {
private IClientConfig config;
private ServerIntrospector serverIntrospector;
protected OverrideRestClient(IClientConfig config,
ServerIntrospector serverIntrospector) {
super();
this.config = config;
this.serverIntrospector = serverIntrospector;
initWithNiwsConfig(this.config);
}
@Override
public URI reconstructURIWithServer(Server server, URI original) {
URI uri = updateToSecureConnectionIfNeeded(original, this.config,
this.serverIntrospector, server);
return super.reconstructURIWithServer(server, uri);
}
@Override
protected Client apacheHttpClientSpecificInitialization() {
ApacheHttpClient4 apache = (ApacheHttpClient4) super.apacheHttpClientSpecificInitialization();
apache.getClientHandler().getHttpClient().getParams().setParameter(
ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
return apache;
}
}
}
自定义全局配置
如何自定义配置呢,例如使用Eureka时需要从注册中心更新服务列表,那么动态更新ServerList就需要用Eureka的实现,需要覆盖RibbonClientConfiguration
中注册的从配置文件中获取的Bean
public ServerList
ribbonServerList(IClientConfig config)
Ribbon提供了一个Bean加载覆盖方式,就是用default.
开头覆盖全局配置,Ribbon提供了RibbonClientConfigurationRegistrar类,其可解析@RibbonClients
注解
@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {
RibbonClient[] value() default {};
// 默认配置,可覆盖全局配置RibbonClientConfiguration
Class<?>[] defaultConfiguration() default {};
}
某个服务可定制配置
@Configuration
@Import(RibbonClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {
String value() default "";
String name() default "";// 服务名,如:order-service
Class<?>[] configuration() default {}; // 关联加载的配置类
}
这两个注解的语义实现是通过IOC容器的钩子接口ImportBeanDefinitionRegistrar
实现
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//1. 解析@RibbonClients注解
Map<String, Object> attrs = metadata.getAnnotationAttributes(
RibbonClients.class.getName(), true);
// @RibbonClients中的定制配置 @RibbonClient
if (attrs != null && attrs.containsKey("value")) {
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
for (AnnotationAttributes client : clients) {
registerClientConfiguration(registry, getClientName(client),
client.get("configuration"));
}
}
// 默认配置
if (attrs != null && attrs.containsKey("defaultConfiguration")) {
String name;
// 名字以default.开头
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
} else {
name = "default." + metadata.getClassName();
}
// 注册
registerClientConfiguration(registry, name, attrs.get("defaultConfiguration"));
}
//2. 解析本类上的@RibbonClient注解
Map<String, Object> client = metadata.getAnnotationAttributes(
RibbonClient.class.getName(), true);
String name = getClientName(client);
if (name != null) {
registerClientConfiguration(registry, name, client.get("configuration"));
}
}
private String getClientName(Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("value");
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (StringUtils.hasText(value)) {
return value;
}
// 定制时,必须指定服务名
throw new IllegalStateException("Either 'name' or 'value' must be provided in @RibbonClient");
}
private void registerClientConfiguration(BeanDefinitionRegistry registry,
Object name, Object configuration) {
// 注册一个RibbonClientSpecification的实例
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(RibbonClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 注册到SpringBoot的Web容器中
registry.registerBeanDefinition(name + ".RibbonClientSpecification",
builder.getBeanDefinition());
}
}
这些配置类最终是可以自动注入到上文说的RibbonAutoConfiguration
中,在构建SpringClientFactory时使用。
public class RibbonAutoConfiguration {
// 自动注入
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
// setter
factory.setConfigurations(this.configurations);
return factory;
}
}
而上文NamedContextFactory#createContext()
方法创建服务访问客户端时,优先注册default.
开头的配置类,再注册全局配置类RibbonClientConfiguration
,从而达到默认的配置优先于全局配置。