最近用到Ribbon,总是觉得Ribbon既强大但是又不好用,其实根源还是对其内部的工作原理不够了解,导致对一些现象不能给出合理的解释,也影响了功能扩展。希望通过本次梳理,能够对Ribbon有一个较为清晰的理解!最近记性也不好了,还是要写下来才有印象。。。-_-
Ribbon + Spring Cloud
Ribbon是Netflix开源的一款用于客户端软负载均衡的工具软件。
Spring Cloud对Ribbon进行了一些封装以更好的使用Spring Boot的自动化配置理念。
先建个环境跑起来
- 搭建一个基本的Spring Boot项目
- 引入Spring Cloud Zuul依赖(Zuul直接包含了Ribbon的依赖,省点事。。。)
org.springframework.cloud
spring-cloud-starter-zuul
1.2.3.RELEASE
- 启用Zuul
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
- 配置路由
server:
port: 7777
zuul:
routes:
node:
path: /api/**
serviceId: node
ribbon:
eureka:
enabled: false
node:
ribbon:
listOfServers: http://localhost:9600,http://localhost:9500
OK,访问http://localhost:7777/api/hello,就可以看到请求在9500和9600间不停切换了。
我在9500,9600启动了两个node express server, 只接受'/hello'请求,返回'Hello,world! {port}'
看看源码
如上,Ribbon发挥了负载均衡的作用,从结果来看,目前ribbon是使用轮询的方式进行负载均衡。为了更好的理解,还是看看代码,他到底怎么工作的?
入口
查看spring-cloud-netflix-core-1.2.3.RELEASE.jar/META-INF/spring.fatories文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration,
...
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,\
很明显,RibbonAutoConfiguration就是Spring Boot启动Rabbion的入口配置类。
RibbonAutoConfiguration
public class RibbonAutoConfiguration {
...
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations); return factory;}
...
}
很明显,最重要的bean就是SpringClientFactory, 为啥?因为其他bean都需要他去初始化。。。
SpringClientFactory
方法列表如图:
查看方法列表,此类提供了获取指定serviceId的IClient, ILoadBalance, ILoadBalanceContext等对象的重要方法。
- IClient 是发起请求并执行的接口,先不管;
- ILoadBalanceContext 封装了一些负载均衡额外的方法,比如noteOpenConnection(), noteError(), noteResponse()等等,先不管。
- ILoadBalance就是我想知道的负载均衡啦。
仔细查看获取LB(LoadBalance)实例的方法,发现LB实例最终从父类NamedContextFactory获取Bean。
Bean来源AnnotationConfigApplicationContext context ,
public Map getInstances(String name, Class type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) {
return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);
}
return null;
}
这个Context从那里获取我想要的bean实例呢?还是截图吧,这个鬼排版傻死了。
看看context的创建过程,上面的红圈处,它会尝试为每个serviceId使用属于自己的配置类(当然前提是你有对应的配置),并注册;
下面的红圈说明,它会注册一个默认的配置类this.defaultConfigType,
this.defaultConfigType = RibbonClientConfiguration.class;
context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
至此,一个关键的配置类被发现了:RibbonClientConfiguration, 不容易-_-!
RibbonClientConfiguration
方法列表:
基本上看着方法名就知道各种Bean是干什么的吧(IRule,IPing,ServerList,ILoadBalancer,ServerListFilter,RibbonLoadBalancerContext,RetryHandler,ServerIntrospector)。
更重要的是:我们可以大胆的为每个serviceId指定自己的配置类了,如下,为"node"指定IPing的特殊的实现类:
@RibbonClient(name = "node", configuration = NodeServiceConfig.class)
public class ZuulApplication {
...
}
public class NodeServiceConfig {
private String name = "node";
@Autowired
private IClientConfig iClientConfig;
@Bean
public IPing ribbonPing(IClientConfig iClientConfig) {
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, iClientConfig, name);
}
return new NoOpPing();
}
}
未完待续...