一、背景介绍
Spring Cloud作为一个企业级的开源微服务框架,一旦涉及到多服务的调用和负载均衡就不可避免地要使用Ribbon。但只使用Spring Cloud和Ribbon结合的话,无法做到多种负载均衡策略的切换。因此,我们需要使用上层的服务发现组件,或者在Spring的上下文环境中定义多个RibbonClient来实现这种策略切换。
二、整合ribbon问题及解决方案
-
典型问题:多个RibbonClient name重名
当RibbonClient配置多个实例时,如果重名了就会出现问题。对于同一Service ID(即RibbonClient),Spring Cloud会启动多个Ribbon实例,这些实例个数和Eureka Server返回的实例个数(ip:port)是一样的,那么多个RibbonClient name重名后就会出现如下报错:
Caused by: java.lang.IllegalStateException: Detected more than one
@LoadBalanced configuration. If you have multiple RestTemplateBuilder
beans they must all have names. One solution is to provide
an actual name for each RestTemplateBuilder with @Bean(name=...)
解决方案:需要在代码中定义RestTemplate,并在请求方法上使用@LoadBalanced注解。如下:
@Configuration
public class RibbonConfiguration {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
// 请求方法
@Service
public class UserServiceImpl implements UserService {
@Autowired
RestTemplate restTemplate;
@Override
@HystrixCommand(fallbackMethod = "getUserInfoFallback")
public Object getUserInfo() {
// 注意这里的url用的是服务名,即RibbonClient的name,而不是IP和Port
String url = "http://user-service/getUserInfo";
return restTemplate.getForObject(url, Object.class);
}
}
-
典型问题:自定义负载均衡策略全局生效
在Spring Cloud中,我们可以定义多个RibbonClient,每个RibbonClient使用固定的负载均衡策略。但是有的时候,我们会需要在某个特定接口中使用多种负载均衡策略。
解决方案:可以使用自定义负载均衡策略,它只对当前Service ID有效并且不影响其他Service ID。
示例1:自定义负载均衡策略
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule() {
return new MyRule();
}
// 自定义规则实现
static class MyRule extends ZoneAvoidanceRule {
@Override
public Server choose(Object key) {
// 自定义逻辑,此处假设总共有3台服务提供商
int num = key instanceof Integer ? (int) key % 3 : 0;
List<Server> servers = this.getLoadBalancer().getAllServers();
return servers.get(num);
}
}
}
示例2:注解方式使用自定义负载均衡策略
public interface UserService {
@RequestMapping(value = "/getUserInfo", method = RequestMethod.GET)
// 示例中的注解和代码必须写在同一个类中
@MyLoadBalanced
public Object getUserInfo();
}
// 自定义注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLoadBalanced {
}
// 自定义工厂实现
@Component
public class MyLoadBalancedFactory extends AbstractLoadBalancerAwareClientFactory implements InitializingBean {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Override
public AsyncClientHttpRequestFactory createAsyncRequestFactory() throws Exception {
return new AsyncLoadBalancerClientHttpRequestFactory(this.loadBalancerClient);
}
@Override
public ClientHttpRequestFactory createRequestFactory() {
return new LoadBalancerClientHttpRequestFactory(this.loadBalancerClient);
}
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
// 注册自定义注解
this.registerLoadBalancerAnnotation(MyLoadBalanced.class, this.loadBalancerClient);
}
}
// 注解工厂管理器
public class RibbonLoadBalancedAnnotationBeanPostProcessor extends AbstractDiscoveryEnabledBeanPostProcessor {
public RibbonLoadBalancedAnnotationBeanPostProcessor(@Autowired(required = false) ObjectProvider<List<LoadBalancerInterceptor>> interceptorsProvider,
@Autowired(required = false) ApplicationContext context,
@Autowired(required = false) Environment environment) {
super(interceptorsProvider, context, environment);
}
// 注册自定义注解工厂
@Override
protected void processInjectionPoint(final InjectionPoint injectionPoint, final Object bean) {
Method method = (Method) injectionPoint.getMember();
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
Class<?> annotationType = annotation.annotationType();
if (isLoadBalancedAnnotation(annotationType)) {
MyLoadBalanced myLoadBalanced = method.getAnnotation(MyLoadBalanced.class);
if (myLoadBalanced != null) {
Object loadBalancedRestTemplate = this.getContext().getBean("loadBalancedRestTemplate");
setField(injectionPoint, bean, loadBalancedRestTemplate);
}
}
}
}
}
以上就是整合Ribbon的常见问题及其解决方案,我们可以按照需求选择合适的方式进行实现。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:spring cloud整合ribbon问题及解决方案 - Python技术站