spring cloud Ribbon用法及原理解析
這篇文章主要介紹了spring cloud Ribbon用法及原理解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
簡介
這篇文章主要介紹一下ribbon在程序中的基本使用,在這里是單獨(dú)拿出來寫用例測試的,實(shí)際生產(chǎn)一般是配置feign一起使用,更加方便開發(fā)。同時這里也通過源碼來簡單分析一下ribbon的基本實(shí)現(xiàn)原理。
基本使用
這里使用基于zookeeper注冊中心+ribbon的方式實(shí)現(xiàn)一個簡單的客戶端負(fù)載均衡案例。
服務(wù)提供方
首先是一個服務(wù)提供方。代碼如下。
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
引導(dǎo)程序,提供了一個ribbonService的rest接口服務(wù),注冊程序到zookeeper中。
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class DiscoverClient {
public static void main(String[] args) {
SpringApplication.run(DiscoverClient.class, args);
}
@RequestMapping("/ribbonService")
public String ribbonService(){
return "hello too ribbon";
}
}
服務(wù)調(diào)用方
服務(wù)調(diào)用方就是進(jìn)行負(fù)載均衡的一方,利用ribbo的RestTemplate進(jìn)行負(fù)載調(diào)用服務(wù)。
RibbonConfig,配置ribbon的RestTemplate,通過@LoadBalanced注解實(shí)現(xiàn),具體原理稍后分析。
@Configuration
public class RibbonConfig {
/**
* 實(shí)例化ribbon使用的RestTemplate
* @return
*/
@Bean
@LoadBalanced
public RestTemplate rebbionRestTemplate(){
return new RestTemplate();
}
/**
* 配置隨機(jī)負(fù)載策略,需要配置屬性service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
*/
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}
引導(dǎo)程序
@SpringBootApplication(scanBasePackages = "garine.learn.ribbon.loadblance")
@EnableDiscoveryClient
@RestController
public class TestRibbonApplocation {
public static void main(String[] args) {
SpringApplication.run(TestRibbonApplocation.class, args);
}
@Autowired
@LoadBalanced
RestTemplate restTemplate;
@GetMapping("/{applicationName}/ribbonService")
public String ribbonService(@PathVariable("applicationName") String applicationName){
return restTemplate.getForObject("http://" + applicationName+"/ribbonService", String.class);
}
}
配置文件同上,服務(wù)名稱修改即可。
測試
啟動兩個discovery-service,由于端口設(shè)置為0,所以是隨機(jī)端口。
啟動服務(wù)調(diào)用方
瀏覽器訪問服務(wù)調(diào)用方的提供的接口,路徑參數(shù)需要加上調(diào)用的服務(wù)名稱,例如http://localhost:8080/discovery-service/ribbonService,然后服務(wù)調(diào)用方使用ribbon的RestTemplate調(diào)用服務(wù)提供方的接口。
結(jié)果返回:hello too ribbon ,同時服務(wù)提供方啟動的兩個服務(wù)都可能被調(diào)用,取決于怎么配置負(fù)載策略。
上面就是一個簡單使用ribbon的例子,結(jié)合feign使用基本上是做類似上面所寫的工作,那么ribbon到底是怎么實(shí)現(xiàn)的呢?
原理與源碼分析
ribbon實(shí)現(xiàn)的關(guān)鍵點(diǎn)是為ribbon定制的RestTemplate,ribbon利用了RestTemplate的攔截器機(jī)制,在攔截器中實(shí)現(xiàn)ribbon的負(fù)載均衡。負(fù)載均衡的基本實(shí)現(xiàn)就是利用applicationName從服務(wù)注冊中心獲取可用的服務(wù)地址列表,然后通過一定算法負(fù)載,決定使用哪一個服務(wù)地址來進(jìn)行http調(diào)用。
Ribbon的RestTemplate
RestTemplate中有一個屬性是List<ClientHttpRequestInterceptor> interceptors,如果interceptors里面的攔截器數(shù)據(jù)不為空,在RestTemplate進(jìn)行http請求時,這個請求就會被攔截器攔截進(jìn)行,攔截器實(shí)現(xiàn)接口ClientHttpRequestInterceptor,需要實(shí)現(xiàn)方法是
ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;
也就是說攔截器需要完成http請求,并封裝一個標(biāo)準(zhǔn)的response返回。
ribbon中的攔截器
在Ribbon 中也定義了這樣的一個攔截器,并且注入到RestTemplate中,是怎么實(shí)現(xiàn)的呢?
在Ribbon實(shí)現(xiàn)中,定義了一個LoadBalancerInterceptor,具體的邏輯先不說,ribbon就是通過這個攔截器進(jìn)行攔截請求,然后實(shí)現(xiàn)負(fù)載均衡調(diào)用。
攔截器定義在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#ribbonInterceptor
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
//定義ribbon的攔截器
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
//定義注入器,用來將攔截器注入到RestTemplate中,跟上面配套使用
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
ribbon中的攔截器注入到RestTemplate
定義了攔截器,自然需要把攔截器注入到、RestTemplate才能生效,那么ribbon中是如何實(shí)現(xiàn)的?上面說了攔截器的定義與攔截器注入器的定義,那么肯定會有個地方使用注入器來注入攔截器的。
在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration#loadBalancedRestTemplateInitializerDeprecated方法里面,進(jìn)行注入,代碼如下。
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
//遍歷context中的注入器,調(diào)用注入方法。
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
//......
}
遍歷context中的注入器,調(diào)用注入方法,為目標(biāo)RestTemplate注入攔截器,注入器和攔截器都是我們定義好的。
還有關(guān)鍵的一點(diǎn)是:需要注入攔截器的目標(biāo)restTemplates到底是哪一些?因?yàn)镽estTemplate實(shí)例在context中可能存在多個,不可能所有的都注入攔截器,這里就是@LoadBalanced注解發(fā)揮作用的時候了。
LoadBalanced注解
嚴(yán)格上來說,這個注解是spring cloud實(shí)現(xiàn)的,不是ribbon中的,它的作用是在依賴注入時,只注入實(shí)例化時被@LoadBalanced修飾的實(shí)例。
例如我們定義Ribbon的RestTemplate的時候是這樣的
@Bean
@LoadBalanced
public RestTemplate rebbionRestTemplate(){
return new RestTemplate();
}
因此才能為我們定義的RestTemplate注入攔截器。
那么@LoadBalanced是如何實(shí)現(xiàn)這個功能的呢?其實(shí)都是spring的原生操作,@LoadBalance的源碼如下
/**
* 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 {
}
很明顯,‘繼承'了注解@Qualifier,我們都知道以前在xml定義bean的時候,就是用Qualifier來指定想要依賴某些特征的實(shí)例,這里的注解就是類似的實(shí)現(xiàn),restTemplates通過@Autowired注入,同時被@LoadBalanced修飾,所以只會注入@LoadBalanced修飾的RestTemplate,也就是我們的目標(biāo)RestTemplate。
攔截器邏輯實(shí)現(xiàn)
LoadBalancerInterceptor源碼如下。
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@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, requestFactory.createRequest(request, body, execution));
}
}
攔截請求執(zhí)行
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
//在這里負(fù)載均衡選擇服務(wù)
Server server = getServer(loadBalancer);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
serviceId), serverIntrospector(serviceId).getMetadata(server));
//執(zhí)行請求邏輯
return execute(serviceId, ribbonServer, request);
}
我們重點(diǎn)看getServer方法,看看是如何選擇服務(wù)的
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
//
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
代碼配置隨機(jī)loadBlancer,進(jìn)入下面代碼
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
//使用配置對應(yīng)負(fù)載規(guī)則選擇服務(wù)
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
這里配置的是RandomRule,所以進(jìn)入RandomRule代碼
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
//獲取可用服務(wù)列表
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
//隨機(jī)一個數(shù)
int serverCount = allList.size();
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
int index = rand.nextInt(serverCount);
server = upList.get(index);
if (server == null) {
/*
* The only time this should happen is if the server list were
* somehow trimmed. This is a transient condition. Retry after
* yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
隨機(jī)負(fù)載規(guī)則很簡單,隨機(jī)整數(shù)選擇服務(wù),最終達(dá)到隨機(jī)負(fù)載均衡。我們可以配置不同的Rule來實(shí)現(xiàn)不同的負(fù)載方式。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Spring mvc是如何實(shí)現(xiàn)與數(shù)據(jù)庫的前后端的連接操作的?
今天給大家?guī)淼氖顷P(guān)于Spring mvc的相關(guān)知識,文章圍繞著Spring mvc是如何實(shí)現(xiàn)與數(shù)據(jù)庫的前后端的連接操作的展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
SpringBoot使用WebJars統(tǒng)一管理靜態(tài)資源的方法
這篇文章主要介紹了SpringBoot使用WebJars統(tǒng)一管理靜態(tài)資源的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
基于@RequestBody和@ResponseBody及Stringify()的作用說明
這篇文章主要介紹了基于@RequestBody和@ResponseBody及Stringify()的作用說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
mybatis QueryWrapper的條件構(gòu)造之a(chǎn)pply、last、select解析
這篇文章主要介紹了mybatis QueryWrapper的條件構(gòu)造之a(chǎn)pply、last、select,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
spring boot與ktor整合的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于spring boot與ktor整合的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09

