看代码说事情:
没有用到注册中心时,服务端的配置如下图所示:
spring:
application:
name: spring-clound-ribbon-consumer
server:
port: 9001
eureka:
client:
enabled: false
# 两个客户端分别是localhost:8080,localhost:8088
helloclient:
ribbon:
listOfServers: localhost:8080,localhost:8088
配置一个RestTemplate,并加上一个@LoadBalanced的注解
package com.chen.cloud.chenribbon.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateT {
@Bean
@LoadBalanced
public RestTemplate template() {
return new RestTemplate();
}
}
controller类中实现一个简单的逻辑
package com.chen.cloud.chenribbon.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
@Controller
public class RibbonController {
@Autowired
private RestTemplate restTemplateT;
@GetMapping("hello")
@ResponseBody
public String getObject() {
//发送请求的地址其实是一个配置文件中的一个配置名加地址
Object result = restTemplateT.getForObject("http://helloclient/test",Object.class);
System.out.println(result);
return "Hello";
}
}
新增两个普通web项目端口号分别是8080,和 8088
8080的client1 项目
package com.chen.cloud.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HelloController {
@GetMapping("test")
public Object getTest(){
System.err.println("进入到了客户端1");
Map<String,String> singleMap = new HashMap<>();
singleMap.put("key","Hello");
return singleMap;
}
}
8088的client2 项目
package com.chen.cloud.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HelloController {
@GetMapping("test")
public Object getTest(){
System.err.println("进入到了客户端2");
Map<String,String> singleMap = new HashMap<>();
singleMap.put("key","Hello");
return singleMap;
}
}
最终效果:
当我在地址栏输入ribbon客户端的地址 :http://localhost:9001/hello
时,ribbon 在调用:http://helloclient/test,时轮训的调用着8080和8089两个端口。
追究原因:发现在@LoadBalanced 上面有一段注释:该注解是用在RestTemplate上,用来配置LoadBalancerClient用的。
package org.springframework.cloud.client.loadbalancer;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient.
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
类的关系图如下所示
LoadBalanceClient中核心方法就是根据serviceId中找到ServiceINstance,具体的实现是在RibbonLoadBalancerClient中,RibbonLoadBalancerClient先去获取了ILoadBalancer。
ILoadBalancer,他是所有服务的处理站。他可以addServer,chooserServer,makeServerDown等一系列方法。因此想要获得server,先去获取ILoadBalancer,还好ILoadBalancer获取不是特别困难,在SpringClientFactory中可以获得。
/**
* A factory that creates client, load balancer and client configuration instances. It
* creates a Spring ApplicationContext per client name, and extracts the beans that it
* needs from there.
*
* @author Spencer Gibb
* @author Dave Syer
*/
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
}
注解中可以了解到该Factory可以创建 IClient,ILoadBalancer,IClientConfig,并且会为每一个客户端的名称创建一个bean容器,并从该容器中取到自己需要的bean实例出来。springcloud 默认取到的是ZoneAwareLoadBalancer(springboot自动装配获得的),ZoneAwareLoadBalancer调用chooseServer时,判断是否配置了zone的相关属性,没有的话会调用BaseLoadBalancer.chooserServer(),并在此处实现了负载均衡的策略。
ZoneAwareLoadBalancer的代码:
public Server chooseServer(Object key) {
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
······························
}
BaseLoadBalancer的代码:
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}