spring cloud Ribbon用法及原理解析
这篇文章主要介绍了springcloudRibbon用法及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
简介
这篇文章主要介绍一下ribbon在程序中的基本使用,在这里是单独拿出来写用例测试的,实际生产一般是配置feign一起使用,更加方便开发。同时这里也通过源码来简单分析一下ribbon的基本实现原理。
基本使用
这里使用基于zookeeper注册中心+ribbon的方式实现一个简单的客户端负载均衡案例。
服务提供方
首先是一个服务提供方。代码如下。
application.properties配置文件
spring.application.name=discovery-service
server.port=0
service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
bootstrap.properties配置文件
spring.cloud.zookeeper.connect-string=192.168.0.15:2181
引导程序,提供了一个ribbonService的rest接口服务,注册程序到zookeeper中。
@SpringBootApplication
@EnableDiscoveryClient
@RestController
publicclassDiscoverClient{
publicstaticvoidmain(String[]args){
SpringApplication.run(DiscoverClient.class,args);
}
@RequestMapping("/ribbonService")
publicStringribbonService(){
return"hellotooribbon";
}
}
服务调用方
服务调用方就是进行负载均衡的一方,利用ribbo的RestTemplate进行负载调用服务。
RibbonConfig,配置ribbon的RestTemplate,通过@LoadBalanced注解实现,具体原理稍后分析。
@Configuration
publicclassRibbonConfig{
/**
*实例化ribbon使用的RestTemplate
*@return
*/
@Bean
@LoadBalanced
publicRestTemplaterebbionRestTemplate(){
returnnewRestTemplate();
}
/**
*配置随机负载策略,需要配置属性service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
*/
@Bean
publicIRuleribbonRule(){
returnnewRandomRule();
}
}
引导程序
@SpringBootApplication(scanBasePackages="garine.learn.ribbon.loadblance")
@EnableDiscoveryClient
@RestController
publicclassTestRibbonApplocation{
publicstaticvoidmain(String[]args){
SpringApplication.run(TestRibbonApplocation.class,args);
}
@Autowired
@LoadBalanced
RestTemplaterestTemplate;
@GetMapping("/{applicationName}/ribbonService")
publicStringribbonService(@PathVariable("applicationName")StringapplicationName){
returnrestTemplate.getForObject("http://"+applicationName+"/ribbonService",String.class);
}
}
配置文件同上,服务名称修改即可。
测试
启动两个discovery-service,由于端口设置为0,所以是随机端口。
启动服务调用方
浏览器访问服务调用方的提供的接口,路径参数需要加上调用的服务名称,例如http://localhost:8080/discovery-service/ribbonService,然后服务调用方使用ribbon的RestTemplate调用服务提供方的接口。
结果返回:hellotooribbon,同时服务提供方启动的两个服务都可能被调用,取决于怎么配置负载策略。
上面就是一个简单使用ribbon的例子,结合feign使用基本上是做类似上面所写的工作,那么ribbon到底是怎么实现的呢?
原理与源码分析
ribbon实现的关键点是为ribbon定制的RestTemplate,ribbon利用了RestTemplate的拦截器机制,在拦截器中实现ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。
Ribbon的RestTemplate
RestTemplate中有一个属性是List
ClientHttpResponseintercept(HttpRequestrequest,byte[]body,ClientHttpRequestExecutionexecution) throwsIOException;
也就是说拦截器需要完成http请求,并封装一个标准的response返回。
ribbon中的拦截器
在Ribbon中也定义了这样的一个拦截器,并且注入到RestTemplate中,是怎么实现的呢?
在Ribbon实现中,定义了一个LoadBalancerInterceptor,具体的逻辑先不说,ribbon就是通过这个拦截器进行拦截请求,然后实现负载均衡调用。
拦截器定义在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#ribbonInterceptor
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
staticclassLoadBalancerInterceptorConfig{
@Bean
//定义ribbon的拦截器
publicLoadBalancerInterceptorribbonInterceptor(
LoadBalancerClientloadBalancerClient,
LoadBalancerRequestFactoryrequestFactory){
returnnewLoadBalancerInterceptor(loadBalancerClient,requestFactory);
}
@Bean
@ConditionalOnMissingBean
//定义注入器,用来将拦截器注入到RestTemplate中,跟上面配套使用
publicRestTemplateCustomizerrestTemplateCustomizer(
finalLoadBalancerInterceptorloadBalancerInterceptor){
returnrestTemplate->{
Listlist=newArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
ribbon中的拦截器注入到RestTemplate
定义了拦截器,自然需要把拦截器注入到、RestTemplate才能生效,那么ribbon中是如何实现的?上面说了拦截器的定义与拦截器注入器的定义,那么肯定会有个地方使用注入器来注入拦截器的。
在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration#loadBalancedRestTemplateInitializerDeprecated方法里面,进行注入,代码如下。
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
publicclassLoadBalancerAutoConfiguration{
@LoadBalanced
@Autowired(required=false)
privateListrestTemplates=Collections.emptyList();
@Bean
publicSmartInitializingSingletonloadBalancedRestTemplateInitializerDeprecated(
finalObjectProvider>restTemplateCustomizers){
//遍历context中的注入器,调用注入方法。
return()->restTemplateCustomizers.ifAvailable(customizers->{
for(RestTemplaterestTemplate:LoadBalancerAutoConfiguration.this.restTemplates){
for(RestTemplateCustomizercustomizer:customizers){
customizer.customize(restTemplate);
}
}
});
}
//......
}
遍历context中的注入器,调用注入方法,为目标RestTemplate注入拦截器,注入器和拦截器都是我们定义好的。
还有关键的一点是:需要注入拦截器的目标restTemplates到底是哪一些?因为RestTemplate实例在context中可能存在多个,不可能所有的都注入拦截器,这里就是@LoadBalanced注解发挥作用的时候了。
LoadBalanced注解
严格上来说,这个注解是springcloud实现的,不是ribbon中的,它的作用是在依赖注入时,只注入实例化时被@LoadBalanced修饰的实例。
例如我们定义Ribbon的RestTemplate的时候是这样的
@Bean
@LoadBalanced
publicRestTemplaterebbionRestTemplate(){
returnnewRestTemplate();
}
因此才能为我们定义的RestTemplate注入拦截器。
那么@LoadBalanced是如何实现这个功能的呢?其实都是spring的原生操作,@LoadBalance的源码如下
/**
*AnnotationtomarkaRestTemplatebeantobeconfiguredtouseaLoadBalancerClient
*@authorSpencerGibb
*/
@Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public@interfaceLoadBalanced{
}
很明显,‘继承'了注解@Qualifier,我们都知道以前在xml定义bean的时候,就是用Qualifier来指定想要依赖某些特征的实例,这里的注解就是类似的实现,restTemplates通过@Autowired注入,同时被@LoadBalanced修饰,所以只会注入@LoadBalanced修饰的RestTemplate,也就是我们的目标RestTemplate。
拦截器逻辑实现
LoadBalancerInterceptor源码如下。
publicclassLoadBalancerInterceptorimplementsClientHttpRequestInterceptor{
privateLoadBalancerClientloadBalancer;
privateLoadBalancerRequestFactoryrequestFactory;
publicLoadBalancerInterceptor(LoadBalancerClientloadBalancer,LoadBalancerRequestFactoryrequestFactory){
this.loadBalancer=loadBalancer;
this.requestFactory=requestFactory;
}
publicLoadBalancerInterceptor(LoadBalancerClientloadBalancer){
//forbackwardscompatibility
this(loadBalancer,newLoadBalancerRequestFactory(loadBalancer));
}
@Override
publicClientHttpResponseintercept(finalHttpRequestrequest,finalbyte[]body,
finalClientHttpRequestExecutionexecution)throwsIOException{
finalURIoriginalUri=request.getURI();
StringserviceName=originalUri.getHost();
Assert.state(serviceName!=null,"RequestURIdoesnotcontainavalidhostname:"+originalUri);
returnthis.loadBalancer.execute(serviceName,requestFactory.createRequest(request,body,execution));
}
}
拦截请求执行
@Override publicTexecute(StringserviceId,LoadBalancerRequest request)throwsIOException{ ILoadBalancerloadBalancer=getLoadBalancer(serviceId); //在这里负载均衡选择服务 Serverserver=getServer(loadBalancer); if(server==null){ thrownewIllegalStateException("Noinstancesavailablefor"+serviceId); } RibbonServerribbonServer=newRibbonServer(serviceId,server,isSecure(server, serviceId),serverIntrospector(serviceId).getMetadata(server)); //执行请求逻辑 returnexecute(serviceId,ribbonServer,request); }
我们重点看getServer方法,看看是如何选择服务的
protectedServergetServer(ILoadBalancerloadBalancer){
if(loadBalancer==null){
returnnull;
}
//
returnloadBalancer.chooseServer("default");//TODO:betterhandlingofkey
}
代码配置随机loadBlancer,进入下面代码
publicServerchooseServer(Objectkey){
if(counter==null){
counter=createCounter();
}
counter.increment();
if(rule==null){
returnnull;
}else{
try{
//使用配置对应负载规则选择服务
returnrule.choose(key);
}catch(Exceptione){
logger.warn("LoadBalancer[{}]:Errorchoosingserverforkey{}",name,key,e);
returnnull;
}
}
}
这里配置的是RandomRule,所以进入RandomRule代码
publicServerchoose(ILoadBalancerlb,Objectkey){
if(lb==null){
returnnull;
}
Serverserver=null;
while(server==null){
if(Thread.interrupted()){
returnnull;
}
//获取可用服务列表
ListupList=lb.getReachableServers();
ListallList=lb.getAllServers();
//随机一个数
intserverCount=allList.size();
if(serverCount==0){
/*
*Noservers.Endregardlessofpass,becausesubsequentpasses
*onlygetmorerestrictive.
*/
returnnull;
}
intindex=rand.nextInt(serverCount);
server=upList.get(index);
if(server==null){
/*
*Theonlytimethisshouldhappenisiftheserverlistwere
*somehowtrimmed.Thisisatransientcondition.Retryafter
*yielding.
*/
Thread.yield();
continue;
}
if(server.isAlive()){
return(server);
}
//Shouldn'tactuallyhappen..butmustbetransientorabug.
server=null;
Thread.yield();
}
returnserver;
}
随机负载规则很简单,随机整数选择服务,最终达到随机负载均衡。我们可以配置不同的Rule来实现不同的负载方式。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。