实践Spring Cloud Ribbon

Spring Cloud Ribbon,基于Ribbon实现的客户端负载均衡解决方案,这里的客户端指的是远程服务。

基本使用

1
2
3
4
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>

自动基于服务名组装服务地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@EnableDiscoveryClient
@SpringBootApplication
public class HelloClientApplication {

	@Bean
	@LoadBalanced
	RestTemplate restTemplate() {
		return new RestTemplate();
	}
    
    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }

	public static void main(String[] args) {
		SpringApplication.run(HelloClientApplication.class, args);
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
public class HelloClientController {
	
	private final Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	private RestTemplate restTemplate;

	@RequestMapping(value = "/helloClient", method = RequestMethod.GET)
	public String hello() {
		logger.info("helloClient received.");
		return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
	}
    
    @GetMapping("/helloClient2")
        public Mono<String> test() {
            Mono<String> result = webClientBuilder.build()
                    .get()
                    .uri("http://HELLO-SERVICE/hello")
                    .retrieve()
                    .bodyToMono(String.class);
            return result;
        }
}

也可以根据服务名查找选择服务,然后拼装服务地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class HelloClientController {
	
	private final Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
    LoadBalancerClient loadBalancerClient;

	@Autowired
	private RestTemplate restTemplate;

	@RequestMapping(value = "/helloClient", method = RequestMethod.GET)
	public String hello() {
		logger.info("helloClient received.");
		ServiceInstance serviceInstance = loadBalancerClient.choose("hello-service");
         String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/hello";
         return restTemplate.getForObject(url, String.class);
	}
}

注:spring-cloud-starter-eureka和spring-cloud-starter-consul-discovery已经依赖了spring-cloud-starter-ribbon

1
2
# 缓存刷新时间间隔
ribbon.ServerListRefreshInterval=30

方法

get

post

put

delete

原理

ribbon-LoadBalancerClient

  • ILoadBalancer

    负载均衡器

    Ribbon-ILoadBalancer

    • BaseLoadBalancer
      • IPing

        检查服务实例是否正常

      • IPingStrategy

        服务检查策略

        • SerialPingStrategy

          线性遍历策略

      • IRule

        负载均衡策略

        ribbon-IRule

        • RoundRobinRule
        • ZoneAvoidanceRule
    • DynamicServerListLoadBalancer:动态更新服务实例清单(支持和从注册中心获取)并过滤

      • ServerList

        服务清单

        ribbon-ServerList

        • DiscoveryEnabledNIWSServerList
      • ServerListUpdater

        服务清单更新

        • UpdateAction
      • ServerListFilter

        服务清单过滤

        ribbon-ServerListFilter

        • ZonePreferenceServerListFilter

          1
          2
          # 基于zone优先
          eureka.instance.metadata-map.zone=zone1
          

    • ZoneAwareLoadBalancer:默认

配置

默认配置类RibbonClientConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@SuppressWarnings("deprecation")
@Configuration
@EnableConfigurationProperties
public class RibbonClientConfiguration {

	@Value("${ribbon.client.name}")
	private String name = "client";

	// TODO: maybe re-instate autowired load balancers: identified by name they could be
	// associated with ribbon clients

    // 实现配置文件中自定义(服务级)
	@Autowired
	private PropertiesFactory propertiesFactory;

	@Bean
	@ConditionalOnMissingBean
	public IClientConfig ribbonClientConfig() {
		DefaultClientConfigImpl config = new DefaultClientConfigImpl();
		config.loadProperties(this.name);
		return config;
	}

	@Bean
	@ConditionalOnMissingBean
	public IRule ribbonRule(IClientConfig config) {
		if (this.propertiesFactory.isSet(IRule.class, name)) {
			return this.propertiesFactory.get(IRule.class, config, name);
		}
		ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
		rule.initWithNiwsConfig(config);
		return rule;
	}

	@Bean
	@ConditionalOnMissingBean
	public IPing ribbonPing(IClientConfig config) {
		if (this.propertiesFactory.isSet(IPing.class, name)) {
			return this.propertiesFactory.get(IPing.class, config, name);
		}
		return new DummyPing();
	}

	@Bean
	@ConditionalOnMissingBean
	@SuppressWarnings("unchecked")
	public ServerList<Server> ribbonServerList(IClientConfig config) {
		if (this.propertiesFactory.isSet(ServerList.class, name)) {
			return this.propertiesFactory.get(ServerList.class, config, name);
		}
		ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
		serverList.initWithNiwsConfig(config);
		return serverList;
	}
	//...

Spring Cloud Ribbon默认实现(没有集成Eureka时) | BeanType | beanName | ClassName | ClassName | 备注 | | ———————— | ———————– | —————————— | —————————————- | —- | | IClientConfig | ribbonClientConfig | DefaultClientConfigImpl | | | | IRule | ribbonRule | ZoneAvoidanceRule | | | | IPing | ribbonPing | NoOpPing | NIWSDiscoveryPing | | | ServerList | ribbonServerList | ConfigurationBasedServerList | DiscoveryEnabledNIWSServerList,DomainExtractingServerList | | | ServerListFilter | ribbonServerListFilter | ZonePreferenceServerListFilter | | | | ILoadBalancer | ribbonLoadBalancer | ZoneAwareLoadBalancer | | | | ServerListUpdater | ribbonServerListUpdater | PollingServerListUpdater | | |

自定义配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class CustomizedRibbonConfiguration{
    @Bean
    public IRule ribbonRule() {
        if (StringUtils.isEmpty(name)) {
            return null;
        }

        // 支持配置文件中按服务自定义
        if (this.propertiesFactory.isSet(IRule.class, name)) {
            return this.propertiesFactory.get(IRule.class, config, name);
        }

        LabelAndWeightMetadataRule rule = new LabelAndWeightMetadataRule();
        rule.initWithNiwsConfig(config);
        return rule;
    }
}

自定义全局ribbon client

1
2
3
4
@Configuration
@RibbonClients(defaultConfiguration = CustomizedRibbonConfiguration.class)
public class AppConfiguration {
}

自定义服务级ribbon client

1
2
3
4
@Configuration
@RibbonClient(name="hello-service" configuration = CustomizedRibbonConfiguration.class)
public class AppConfiguration {
}

备注:CustomizedRibbonConfiguration不要被@ComponentScan扫描到,否则是全局共享的

配置文件

属性

ribbon相关属性定义参见CommonClientConfigKey

1
2
# 全局配置
ribbon.<key>=<value>
1
2
# 服务级配置
<clientName>.ribbon.<key>=<value>

例如:

1
2
3
4
5
6
7
8
9
# 自定义客户地址(不通过Eureka)
hello-service:
  ribbon:
    listOfServers: example.com,google.com
# 启动加载(默认延时加载)    
ribbon:
  eager-load:
    enabled: true
    clients: hello-service, user-service

例如:

1
2
# 不使用eureka
ribbon.eureka.enabled = false

服务级ribbon client

1.2.0开始支持,目前支持如下key:

1
2
3
4
5
NFLoadBalancerRuleClassName=
NFLoadBalancerPingClassName=
NIWSServerListClassName=
NIWSServerListFilterClassName=
NFLoadBalancerClassName=

例如:

1
hello-service.ribbon.NFLoadBalancerRuleClassName=com.github.charlesvhe.springcloud.practice.core.LabelAndWeightMetadataRule

备注:不支持配置全局ribbon client,除非扩展PropertiesFactory