- Ribbon简介
Ribbon是Netfix公司开源负载均衡组件,负载均衡是分布式系统高可用、缓解网络压力的重要手段;负载均衡分为硬件负载均衡和软件负载均衡:
- 硬件负载均衡:在服务器间挂载负载均衡的设备,以提供负载均衡的功能
- 软件负载均衡:通过逻辑代码将HTTP或者TCP请求进行转发,达到负载均衡的功能
Ribbon就是通过软件达到负载均衡的功能,Ribbon客户端加载负载均衡的组件以满足HTTP和TCP请求转发的功能。客户端需要达到负载均衡的功能就必须和注册中心合作(EUREKA);
将RestTemplate和 Ribbon中的@LoadBalanced结合起来就可以实现客户端负载均衡的功能;
RestTemplate简介
RestTemplate是消费REST服务的网络请求框架,提供消费常见HTTP协议的方法,如GET、POST、DELETE、PUT...Ribbon 实现负载均衡
3.1 RestTemplate加入@LoadBalanced
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
3.2 使用LoadBalancerClient获取服务列表
@RestController
@Slf4j
public class InfoController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("users/{id}")
public User getUserOrder(@PathVariable("id") Long id){
ServiceInstance serviceInstance = loadBalancerClient.choose("user-service");
log.info("{}:{}:{}",serviceInstance.getServiceId(),serviceInstance.getHost(),serviceInstance.getPort());
return restTemplate.getForEntity("http://user-service/users/"+id,User.class).getBody();
}
}
3.3 网页访问http://localhost:8891/user1/2,会根据默认的负载均衡规则选择不同的服务,下街详细介绍Ribbon的负责均衡的规则。
-
阐述Ribbon工作流程
4.1 我们从根源LoadBalancerClient入手,我们先看看它的类结构图;
我们在demo中使用loadBalancerClient.choose("user-service"); 所以我们在RibbonLoadBalancerClient类中查看choose方法;
public ServiceInstance choose(String serviceId, Object hint) {
Server server = getServer(getLoadBalancer(serviceId), hint);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
此方法返回一个ServiceInstance对象,得到根据负载均衡规则得到的服务,看到这里好像只得到负载均衡的服务并没什么关于RestTemplate负载均衡调用服务;
这时突然想到我们注入RestTemplate时加入了@LoadBalanced注解,我们先看看注解;
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier //画重点很重要
public @interface LoadBalanced {
}
从注解的众多声明中,有一个@Qualifier在这里尤为重要;这时我们全文搜索@LoadBalanced,发现LoadBalancerAutoConfiguration类中用@LoadBalanced注解维护了一个RestTemplate的列表;
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
关于如何维护,请看下面两段代码
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
这段代码将标注了@LoadBalanced注解的RestTemplate注入(这就是说刚才那个@Qualifier很重要的原因了,因为@LoadBalanced加入@Qualifier才具备注入的功能)
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
这段代码将注入的RestTemplate增加了一个拦截器,此拦截器就是实现负载均衡的关键,打开loadBalancerInterceptor中的intercept方法;
@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));
}
这段代码清晰的表明,restTemplate发起的请求被拦截后交给了LoadBalancerClient处理,继而交给ILoadBalance去根据配置(这里先叫配置,等会详解)去选择对应的服务;
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
所以这里我们又要去找ILoadBalance的源码了.,老规矩先看看类结构图;
public interface ILoadBalancer {
public void addServers(List<Server> newServers);
public Server chooseServer(Object key);
public void markServerDown(Server server);
public List<Server> getServerList(boolean availableOnly);
public List<Server> getReachableServers();
public List<Server> getAllServers();
}
该接口中定义了一个chooseServer方法,来返回服务,我们在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;
}
}
}
这里就探究IRule的详细规则。
- RoundRobinRule:轮询
- RandomRule:随机
- AvailabilityFilteringRule:过滤不可用服务,再做轮询
- WeightedResponseTimeRule:根据权重来选择响应时间最小的服务
- RetryRule:轮询加重试机制
- BestAvailableRule:并发量最小的服务
- ZoneAvoidanceRule:复合判断server所在区域的性能和server的可用性选择server
下面贴上修改Ribbon默认IRule的代码。
定义随机选择的Bean
@Configuration
class ExcludeConfigBean{
@Bean
public IRule getIRule(){
return new RandomRule();
}
}
避免你定义的规则被@ComponentScan扫描,会被RibbonClient共享从而冲突,所以需要排序此Bean被扫描。
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@ComponentScan(basePackages = {"com.ybb"}, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {ExcludeConfigBean.class}))
@RibbonClient(name= "user-service", configuration = RandomRule.class)
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
代码运行结果请求两个服务是没有任何规律的,如图: