說的多,也要作的多,這樣才踏實多。java
1.基礎知識2.Netflix Ribbon主要組件2.1 ServerList 服務列表2.2 ServerListFilter服務篩選2.3 ServerListUpdater 服務列表的更新2.4 IPing 服務存活檢查2.5 IRule 根據算法選擇一個服務2.6 ILoadBalancer:統籌者,總管2.7 配置組件組合方式3.SC-Ribbon主要組件(脫離Eureka)3.1 (配置)默認的配置RibbonClientConfiguration3.2 (配置)自定義配置3.2.1 註解式配置類3.2.1 配置文件裏配置:3.3 (使用) SpringClientFactory3.4 (使用) LoadBalancerClient3.SC-Ribbon與eureka一塊兒使用又發生了什麼4.Resttemplate 如何具備負載均衡能力5.總結git
首先要明白一個基礎知識點:github
Netfix公司開源了一系列微服務組件。項目地址github.com/Netflix。裏面有eureka
,Ribbon
等等web
SpringCloud 集成了Netfix開源的套件的幾個套件,也組成了一個項目,項目地址github.com/spring-clou…。咱們熟悉 eureka,ribbon就在這裏。算法
Ribbon是Netflix發佈的開源項目,主要功能是提供客戶端的軟件負載均衡算法spring
Spring Cloud Ribbon模塊是封裝
了Netflix公司開發的這個Ribbon項目。api
也就是說緩存
本文基於Brixton版本,springboot環境下直接看的源碼springboot
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>ml
複製代碼
先從Netflix Ribbon
項目的角度去看服務器
存儲服務列表,也就是被負載均衡的對象
主要實現
Eureka Client
中獲取服務列表。篩選特定條件的服務列表,對ServerList服務器列表進行二次過濾
主要實現
:
定義關於更新動做,根據策略執行更新操做,更新ServerList列表,通常用於動態ServerList
主要實現
:
PollingServerListUpdater
默認的實現策略。此對象會啓動一個定時線程池,定時執行更新策略
EurekaNotificationServerListUpdater
當收到緩存刷新的通知,會更新服務列表。
檢查一個服務是否存活,
主要實現
:
IPing檢查的是當個服務的存活性。
IPingStrategy
:服務列表檢查策略接口,
惟一實現:SerialPingStrategy
:採用線性遍歷的策略方式,使用IPing檢查每一個服務的存活性。
根據算法中從服務列表中選取一個要訪問的服務
主要實現
:
很是好
) 剛啓動時,統計數據不足,先使用RoundRobinRule
策略。有個DynamicServerWeightTask
定時任務,默認每隔30秒會計算一次各個服務實例響應時間,等統計信息足夠,切換到WeightedResponseTimeRule
.小結:咱們能夠看出上述組件是對 服務列表(
待負載對象
)的定義
,與操做
。可是要想讓他們協同起來幹活。這就須要ILoadBalancer
負載均衡器,負責負載均衡調度的管理,總管級別。
調度其餘組件,完成從服務列表裏獲取一個服務的目標
對外提供一個方法,選擇出一個Server來。
public Server chooseServer(Object key);
複製代碼
抽象類:AbstractLoadBalancer 主要定義了服務的分組
主要實現
:
1.BaseLoadBalancer: 基本負載均衡器實現.維護
(1)維護一個全部服務列表+當前存活服務列表
(2)默認使用輪訓選擇一個服務
(3)定義一個定時器,根據SerialPingStrategy策略定時檢活
2.DynamicServerListLoadBalancer動態升級版
(1)DomainExtractingServerList 動態從EurekaClient獲取UP狀態服務列表
(2)ZoneAffinityServerListFilter對列表進行過濾
(3)PollingServerListUpdater定時對服務進行更新
3.ZoneAwareLoadBalancer: 在DynamicServerListLoadBalancer基礎上增長了zone策略。
(1)此類使用ZoneStats存儲每一個Zone的狀態和平均請求狀況。區域指標做爲選擇服務的影響因素
組件有了,到底採用哪一種組合方式呢?Ribbon爲使用者提供了可配置化。
經過不一樣的組件組合,配置不一樣的負載均衡效果。
配置的方式有哪些呢:
DefaultClientConfigImpl
。你不配置任何屬性,則默認會使用這裏面定義的組件。固然這裏還有其餘各類屬性的配置默認配置的組件有:
複製代碼
<clientName>.<nameSpace>.<propertyName>=<value>
api-user.ribbon.listOfServers=localhost:8099
複製代碼
這樣:Netfix-Ribbon的大概組件,以及工做原理也就一目瞭然了
項目地址github.com/Netflix/rib…
SpringCloud-Ribbon集成了Netflix Ribbon
,使其融入springcloud體系。
核心的功能都在Netflix Ribbon
項目中了。SC-Ribbon封裝Netflix Ribbon
,主要作了一些集成工做。
爲此,SpringCloud-Ribbon又增長了哪些組件,幹了哪些事呢?
思考
:既然核心功能在Netflix Ribbon
項目中了。此項目乾的事,無非就是從如下幾方面入手
!!!!!注意注意: 此處說的是脫離Eureka使用SpringCloud-Ribbon
下面咱們來看爲此作出的工做:
配置層面的工做:
此配置類也是做爲一個默認配置類存在,也就是在你不配置任何東西是SpringCloud-Ribbon
默認裝載的核心組件的組合,
相比於Netflix Ribbon
項目DefaultClientConfigImpl
的提供的默認組件爲
本着可擴展原則: SpringCloudRibbon也要提供個性化配置功能。
這裏涉及兩個註解:@RibbonClient
與@RibbonClients
(注意有個s
)
RibbonClientConfiguration
替換默認的配置使用方式以下:我把user
服務負載均衡的IPing
組件替換爲個人MyPingUrl
。
Configuration
@RibbonClient(name = "user", configuration = UserConfiguration.class)
public class UserRibbonConfiguration {
}
@Configuration
protected static class UserConfiguration{
@Bean
public IPing ribbonPing() {
return new MyPingUrl();
}
}
複製代碼
注意 添加UserConfiguration中的配置。UserConfiguration必須使用@Configuration進行聲明,並且不能放在能夠被main application context的@ComponentScan或@SpringBootApplication掃描到的地方,防止該配置被全部的@RibbonClient共享
若是想把全部服務的負載均衡的的IPing
組件替換爲個人MyPingUrl
@RibbonClient(defaultConfiguration = UserConfiguration.class)
public class UserRibbonConfiguration {
}
複製代碼
工做原理:
這兩個註解的工做原理是:經過引入RibbonClientConfigurationRegistrar
註冊器,將配置類註冊爲一個RibbonClientSpecification
@Configuration
@Import(RibbonClientConfigurationRegistrar.class)//這裏
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {
}
//註冊器
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
private void registerClientConfiguration(BeanDefinitionRegistry registry,
Object name, Object configuration) {
//註冊爲一個RibbonClientSpecification
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(RibbonClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + ".RibbonClientSpecification",
builder.getBeanDefinition());
}
}
複製代碼
RibbonClientSpecification
: 直譯爲客戶端規範,對,你的個性化配置被解析爲一個客戶端規範
舉例說明爲:
RibbonClientSpecification
規範RibbonClientSpecification
規範這個規範是如何生效的呢?見下文
另外一種就是直接在spring體系內的配置文件中配置application.yml
api-user:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
複製代碼
在這些屬性中定義的類優先於使用@RibbonClient(configuration=MyRibbonConfig.class)定義的bean和由Spring Cloud Netflix提供的默認值。
說了配置,在講講使用層面的工做
使用層面的工做:
負載均衡功能的使用其實就在ILoadBalancer
這個統籌組件上。所謂使用,其實就是就在這個ILoadBalancer
上。
SpringClientFactory使用工廠模式對外提供:根據serviceId獲取IClient, ILoadBalance, ILoadBalanceContext等對象的服務。
這裏咱們只關注
ILoadBalance
的獲取。
SpringClientFactory 爲每一個服務建立一個獨立的上下文,並在其中加載對應的配置及Ribbon核心接口的實現類。建立邏輯在其父類NamedContextFactory
public abstract class NamedContextFactory{
private Map<String, C> configurations = new ConcurrentHashMap<>();
private Class<?> defaultConfigType;
//建立上下文
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object> singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
}
context.refresh();
return context;
}
}
複製代碼
這裏有兩個點:
根據傳入服務serverId
建立一個AnnotationConfigApplicationContext
,裝載屬於他本身的負載均衡套件。不一樣的服務有不一樣的上下文,裏面存儲的是屬於他的負載均衡套件組合
服務的負載均衡套件組合方式。是上文提到的兩種配置方式決定的。也就是configurations
與defaultConfigType
兩個屬性所表明的規範。
private Map<String, C> configurations = new ConcurrentHashMap<>();
private Class<?> defaultConfigType;//表明默認的組件組合
複製代碼
RibbonClientSpecification
RibbonClientConfiguration
當咱們調用SpringClientFactory.getLoadBalancer
方法獲取一個負載均衡器實例時, 本質就是在這個服務對應的上下文中取出屬於他的ILoadBalancer
實現。
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);返回bean
}
return null;
}
複製代碼
獲取到ILoadBalancer
實現,咱們就能夠調用其chooseServer
得到一個服務實例,進行請求了。
ILoadBalancer userLoadBalancer = clientFactory.getLoadBalancer("user");
Server server = userLoadBalancer.chooseServer("default")
複製代碼
LoadBalancerClient封裝了SpringClientFactory, 做爲負載均衡器客戶端,被普遍使用。這個類纔是SC-Ribbon的真正負載均衡客戶端的入口。
提供了三個方法:
每一個方法都是與負載均衡有關
ServiceInstance choose(String serviceId);
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
URI reconstructURI(ServiceInstance instance, URI original);
複製代碼
LoadBalancerClient做爲負載均衡客戶端。他的能力徹底是創建在以上全部組件的共同努力下實現的。
public class MyClass {
@Autowired
private LoadBalancerClient loadBalancer;
public void doStuff() {
ServiceInstance instance = loadBalancer.choose("stores");
URI storesUri = URI.create(String.format("http://%s:%s", instance.getHost(), instance.getPort()));
// ... do something with the URI
}
}
複製代碼
1.配置層面:
當SpringCloudRibbon
與Eureka
一塊兒使用時,會使用@RibbonClients
爲全部的服務的負載均衡替換一些默認組件(更換組合套件)
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(DiscoveryEnabledNIWSServerList.class)
@ConditionalOnBean(SpringClientFactory.class)
@ConditionalOnProperty(value = "ribbon.eureka.enabled", matchIfMissing = true)
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {
}
----------------------------
@Configuration
@CommonsLog
public class EurekaRibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
NIWSDiscoveryPing ping = new NIWSDiscoveryPing();//
ping.initWithNiwsConfig(config);
return ping;
}
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config) {
DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
config);
DomainExtractingServerList serverList = new DomainExtractingServerList(
discoveryServerList, config, this.approximateZoneFromHostname);
return serverList;
}
}
複製代碼
DomainExtractingServerList
。DomainExtractingServerList
代理了DiscoveryEnabledNIWSServerList
。DiscoveryEnabledNIWSServerList
存儲的服務列表都從EurekaClient
本地緩存裏取到的。(取操做是PollingServerListUpdater
,定時執行的)public class DiscoveryEnabledNIWSServerList extends AbstractServerList<DiscoveryEnabledServer>{
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
EurekaClient eurekaClient = eurekaClientProvider.get();
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
....
}
}
複製代碼
2.使用層面:
沒啥變化,就是使用LoadBalancerClient
來使用負載均衡獲取一個Server。
Resttemplate 具備負載均衡能力,其本質仍是經過使用LoadBalancerClient
來實現。
如何實現呢?
這就涉及到兩個知識點:
Resttemplate
相關體系: @LoadBalanced
註解。Resttemplate
是有攔截器的概念的 。也就是說在真正發起請求以前,會走一些攔截器。這就給負載均衡選擇一個Server
提供了機會。
@LoadBalanced
註解就是給其修飾的Resttemplate
實例,注入LoadBalancerInterceptor
攔截器。此攔截器就是經過LoadBalancerClient
來實現了負載均衡
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
...
}
複製代碼
具體可閱讀個人其餘文章
發送http請求(1):發送http請求的幾種方式
發送http請求(2):RestTemplate發送http請求
@LoadBalanced註解的RestTemplate擁有負載均衡的能力
文本從介紹負載均衡的幾個組件開始,由底層講到了Resttemplate
原理。打開了Resttemplate
發起負載均衡請求的黑盒。順着這條線,只要仔細研讀各個部分的知識點。
你會感嘆開源框架的大廈的創建,爲咱們省去了多少開發工做。
咱們不該該是停留在使用的階段,還應該深刻去了解底層框架的原理,這樣你纔會在大廈之上更進一層。
參考連接:
https://www.jianshu.com/p/73c117fbfe10
https://blog.csdn.net/hry2015/article/details/78357990
若是本文任何錯誤,請批評指教,不勝感激 !
若是文章哪些點不懂,能夠聯繫我交流學習!
微信公衆號:源碼行動
享學源碼,行動起來,來源碼行動