docs/Spring全家桶/SpringCloud源码分析/SpringCloudRibbon源码分析.md
ѧϰĿ
Ҫףspring-cloud-starter-netflix-ribbonjar֪ǻstarterģ϶springbootԶװԭʱṩһԶ࣬ҪõĶע뵽IoCȥˣӹɡ
ȻҪRestTemplateȥĿʱʱǿ϶ҪʵIPͶ˿滻һʵǺIJ裬Ҫôأô֮ǰַͶ˿è̫ӻʵأ
ճУӦ֪һһʵɣԣʵϾڻȡRestTemplateʱöһRestTemplateִijʱȥִһ顣Ȼˡ
ͼƵ£2 װRibbonʵ Ƶ̣ǽʵһװRibbon
IJҪ˼·
1.ҪʵһstarterspringbootspringbootʱõӦRestTemplatebeanһMavenquickstartĿ
2.Ȼ
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
myribbon-spring-cloud-starter
<version>1.0-SNAPSHOT</version>
<name>myribbon-spring-cloud-starter</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
</properties>
<!-- RestTemplateҪõSpringMVC -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
spring-boot-starter-web
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
spring-boot-starter-test
<version>${spring-boot.version}</version>
</dependency>
<!-- 漯һЩĽӿ -->
<dependency>
<groupId>org.springframework.cloud</groupId>
spring-cloud-commons
<version>2.2.6.RELEASE</version>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
maven-clean-plugin
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
maven-resources-plugin
<version>3.0.2</version>
</plugin>
<plugin>
maven-compiler-plugin
<version>3.8.0</version>
</plugin>
<plugin>
maven-surefire-plugin
<version>2.22.1</version>
</plugin>
<plugin>
maven-jar-plugin
<version>3.0.2</version>
</plugin>
<plugin>
maven-install-plugin
<version>2.5.2</version>
</plugin>
<plugin>
maven-deploy-plugin
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
maven-site-plugin
<version>3.7.1</version>
</plugin>
<plugin>
maven-project-info-reports-plugin
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
@Configuration
public class MyRibbonAutoConfiguration {
//װRibbonǿȥɸؾ㷨ԼʵipͶ˿滻
@Bean
public LoadBalancerClient loadBalancerClient(){
return new MyLoadBalancerClient();
}
//ռдMyLoadBalancedעRestTemplate
@MyLoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient);
}
//Ǻĵ
@Bean
public MyLoadBalancerInterceptor myLoadBalancerInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory){
return new MyLoadBalancerInterceptor(loadBalancerClient,requestFactory);
}
//ռRestTemplateﶼһ
@Bean
public SmartInitializingSingleton smartInitializingSingleton(
final MyLoadBalancerInterceptor myLoadBalancerInterceptor){
return ()->{
for (RestTemplate restTemplate : MyRibbonAutoConfiguration.this.restTemplates) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(myLoadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
4.MyLoadBalancerClient
public class MyLoadBalancerClient implements LoadBalancerClient {
@Autowired
AbstractEnvironment environment;
//1.
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
ServiceInstance server = this.choose(serviceId);
return execute(serviceId, server, request);
}
//3.ִHttp
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
T returnVal = null;
try {
returnVal = request.apply(serviceInstance);
} catch (Exception e) {
e.printStackTrace();
}
return returnVal;
}
//4.һʵipport滻
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
String host = instance.getHost();
int port = instance.getPort();
if (host.equals(original.getHost())
&& port == original.getPort()
) {
return original;
}
try {
StringBuilder sb = new StringBuilder();
sb.append("http").append("://");
if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
sb.append(original.getRawUserInfo()).append("@");
}
sb.append(host);
if (port >= 0) {
sb.append(":").append(port);
}
sb.append(original.getRawPath());
if (!Strings.isNullOrEmpty(original.getRawQuery())) {
sb.append("?").append(original.getRawQuery());
}
if (!Strings.isNullOrEmpty(original.getRawFragment())) {
sb.append("#").append(original.getRawFragment());
}
URI newURI = new URI(sb.toString());
return newURI;
}catch (URISyntaxException e){
throw new RuntimeException(e);
}
}
//2.ؾ㷨һзipͶ˿ѡõ㷨
@Override
public ServiceInstance choose(String serviceId) {
Server instance = new Server(serviceId,null,"127.0.0.1",8080);
String sr = environment.getProperty(serviceId+".ribbon.listOfServers");
if (!StringUtils.isEmpty(sr)){
String[] arr = sr.split(",",-1);
Random selector = new Random();
int next = selector.nextInt(arr.length);
String a = arr[next];
String[] srr = a.split(":",-1);
instance.setHost(srr[0]);
instance.setPort(Integer.parseInt(srr[1]));
}
return instance;
}
}
5.ʵܼhttp֮ǰִҵ
public class MyLoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancerClient;
private LoadBalancerRequestFactory requestFactory;
public MyLoadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
this.loadBalancerClient = loadBalancerClient;
this.requestFactory = requestFactory;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return this.loadBalancerClient.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
6.Լע
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface MyLoadBalanced {
}
7.һԼServerʵ
public class Server implements ServiceInstance {
private String serviceId;
private String instanceId;
private String host;
private int port;
public Server(String serviceId, String instanceId, String host, int port) {
this.serviceId = serviceId;
this.instanceId = instanceId;
this.host = host;
this.port = port;
}
public Server() {
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
public void setInstanceId(String instanceId) {
this.instanceId = instanceId;
}
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
@Override
public String getInstanceId() {
return null;
}
@Override
public String getServiceId() {
return null;
}
@Override
public String getHost() {
return host;
}
@Override
public int getPort() {
return port;
}
@Override
public boolean isSecure() {
return false;
}
@Override
public URI getUri() {
return null;
}
@Override
public Map<String, String> getMetadata() {
return null;
}
@Override
public String getScheme() {
return null;
}
}
8.дspring.factoriesļ
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.config.MyRibbonAutoConfiguration
9.jarԣԵʱҪļ
<serverName>.ribbon.listOfServers=127.0.0.1:2223,127.0.0.1:2222
3 Դ֤ 3.1 @LoadBalanced ϽڿεĴ뿴ֻRestTemplateһ@LoadBalance,Ϳʵָؾˣǵ@LoadBalanceһ£עһ@Qualifierע⡣עĸbeanӦñԶע롣SpringжϳĸbeanӦñעʱ@QualifierעbeanԶע룬Ӽ롣
/**
* Annotation to mark a RestTemplate or WebClient 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 {
}
עп֪עRestTemplateǣʹøؾͻˣLoadBalancerClientԣɵRestTemplatebeanôһע⣬beanͻLoadBalancerClient
3.2 LoadBalancerClient ôٿLoadBalancerClientĴ룺
public interface LoadBalancerClient extends ServiceInstanceChooser {
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
<T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException;
URI reconstructURI(ServiceInstance instance, URI original);
}
public interface ServiceInstanceChooser {
ServiceInstance choose(String serviceId);
}
LoadBalancerClientһӿڣ
ServiceInstance choose(String serviceId);ӷϾͿԿǸݴserviceIdӸؾѡһʵʵͨServiceInstanceʾ executeʹôӸؾѡķʵִݡ URI reconstructURI(ServiceInstance instance, URI original);¹һURIģǵڴУͨRestTemplateʱдǷɣͻURIתhost+portͨhost+portʽȥ 3.3 Զװ springboot֮ͨԶװԶȥ spring-cloud-netflix-ribbonjarMETA-INFĿ¼spring.factoriesļҽRibbonAutoConfigurationע롣RibbonAutoConfigurationΪ@AutoConfigureBeforeע⣬ֻLoadBalancerAutoConfigurationࡣLoadBalancerAutoConfigurationУspringὫб@LoadBalanceעεbeanע뵽IOC
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
ͬʱLoadBalancerAutoConfigurationлΪÿRestTemplateʵLoadBalancerInterceptor
RibbonAutoConfigurationעLoadBalancerClientӿڵʵRibbonLoadBalancerClient
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
**3.4 ** ԶУrestTemplateʵLoadBalancerInterceptorԣrestTemplatehttpʱͻִintercept
interceptУrequest.getURI()ȡuriٻȡhostڷhttpʱõķΪhostԣͻõٵþLoadBalancerClientʵexecute
LoadBalancerClientʵΪRibbonLoadBalancerClientյĸؾִУԣҪRibbonLoadBalancerClient
ȿRibbonLoadBalancerClientеexecute
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException {
Server server = null;
if (serviceInstance instanceof RibbonServer) {
server = ((RibbonServer) serviceInstance).getServer();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
// catch IOException and rethrow so RestTemplate behaves correctly
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
ΪserviceIdֶδͨgetLoadBalancerȡloadBalancerٸloadBalancerȡservergetServerĴ룺
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");
}
loadBalancerΪգֱӷؿգ͵loadBalancerchooseServerȡӦserver
һILoadBalancerһӿڣһϵиؾʵֵķ
public interface ILoadBalancer {
public void addServers(List<Server> newServers);
public Server chooseServer(Object key);
public void markServerDown(Server server);
@Deprecated
public List<Server> getServerList(boolean availableOnly);
public List<Server> getReachableServers();
public List<Server> getAllServers();
}
ЩȽֱۣܲ³ǸɶģaddServersһserverϣchooseServerѡһservermarkServerDownijߣgetReachableServersȡõServerϣgetAllServersǻȡеserverϡ ILoadBalancerкܶʵ֣ǾõĸأRibbonAutoConfigurationעSpringClientFactoryͨRibbonClientConfigurationڳʼʱZoneAwareLoadBalancerΪؾ
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
3.5 ZoneAwareLoadBalancer ZoneAwareLoadBalancerпԿؾzoneйϵġ濴ZoneAwareLoadBalancerеchooseServer
eurekaṩregionzoneзѷAWS regionԼΪϵķٻ߱ȵȣûоСơĿкregion zoneԼΪregionڵľ˵regionΪȻͿڴregion֮»ֳzone1,zone2zone
@Override
public Server chooseServer(Object key) {
//ֻеؾάʵZoneĸ1ʱŻִѡ
//ʹøʵ
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
Server server = null;
try {
LoadBalancerStats lbStats = getLoadBalancerStats();
//ΪǰؾеZoneֱգzoneSnapshotУЩеں㷨
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
logger.debug("Zone snapshots: {}", zoneSnapshot);
if (triggeringLoad == null) {
triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
}
if (triggeringBlackoutPercentage == null) {
triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
}
//ÿZoneļϣgetAvailableZonesͨzoneSnapshotʵֿѡ
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
//ѡһZone
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
//öӦĸؾ
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
//ѡķʵ
//chooseServerнʹIRuleӿڵchooseѡʵIRuleӿڵʵֻʵZoneAvoidanceRuleѡķʵ
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception e) {
logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
}
if (server != null) {
return server;
} else {
logger.debug("Zone avoidance logic is not invoked.");
return super.chooseServer(key);
}
}
serverzoneͿѡʵһServer
ļ
3.5.1 DynamicServerListLoadBalancer
ZoneAwareLoadBalancerĸ
ఴ˵Ƕ̬طбʹõġмȽҪķ
DynamicServerListLoadBalancerĺľǻȡбEurekaĬͨDomainExtractingServerListȡorg.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonClientConfiguration#ribbonServerListûмEurekaʱ
3.5.2 BaseLoadBalancer
DynamicServerListLoadBalancerĸ
Ĭֵ
pingЩʲô
PingTaskΪһ߳ǶڼǷServerListUpdater»ƲͻribbonԼάһƣҪΪ˽ͷʧܵĸʡĬʹeurekaʱpingʹõNIWSDiscoveryPingɷ⡣eureka ServerListUpdaterˢ·биõĶʱ˳ķҾԼдʱҲʹá
ͬһʱִʱ䳬˶ʱڣôһʱһʱûִʱȡҲ˺ܶƣзʵһµĶʱʹõǶǸallServersֻܶдڷpingͨķnewUpListУͨдupServerListס
ֻһдҲܶpingڼйڶдԭʹá
Ҫ̾ǣ
pingServersǼ
BaseLoadBalancerܼ
applyĶ壺
public interface LoadBalancerRequest<T> {
T apply(ServiceInstance instance) throws Exception;
}
ʱribbonServerServiceInstance͵ĶнաServiceInstanceһӿڣ˷ϵͳУÿʵҪṩϢserviceIdhostportȡ
LoadBalancerRequestһӿڣջͨʵapplyȥִУʵLoadBalancerInterceptorеRibbonLoadBalancerClientexecuteʱһ࣬ͨ鿴LoadBalancerInterceptorĴ뿴
LoadBalancerRequestʱдapplyapplyУ½һServiceRequestWrapperڲ࣬УдgetURIgetURIloadBalancerreconstructURIuri
ѾԴ֪RibbonʵָؾˣRestTemplateע⣬ͻLoadBalancerClientĶҲRibbonLoadBalancerClientͬʱ LoadBalancerAutoConfigurationãһLoadBalancerInterceptorõrestTemplateЩrestTemplateLoadBalancerInterceptor
ͨrestTemplateʱͻᾭУͻRibbonLoadBalancerClientеķȡݷͨؾⷽȡʵȻȥʵ
3.7 ȡб ˵Щζиؾģǻи⣬ʵǴEureka Serverϻȡģʵбλȡأô֤ʵбеʵǿõأ
RibbonLoadBalancerClientѡʵʱͨILoadBalancerʵݸؾ㷨ѡʵģҲZoneAwareLoadBalancerchooseServerеǾ鿴ZoneAwareLoadBalancerļ̳йϵԿͼʾԿILoadBalancerӿڣAbstractLoadBalancer̳ӿڣBaseLoadBalancer̳AbstractLoadBalancer࣬ DynamicServerListLoadBalancer̳BaseLoadBalancerZoneAwareLoadBalancer̳DynamicServerListLoadBalancer
ILoadBalancerӿڵĴѾˣڿAbstractLoadBalancerĴ룺
public abstract class AbstractLoadBalancer implements ILoadBalancer {
public enum ServerGroup{
ALL,
STATUS_UP,
STATUS_NOT_UP
}
/**
* delegate to {@link #chooseServer(Object)} with parameter null.
*/
public Server chooseServer() {
return chooseServer(null);
}
/**
* List of servers that this Loadbalancer knows about
*
* @param serverGroup Servers grouped by status, e.g., {@link ServerGroup#STATUS_UP}
*/
public abstract List<Server> getServerList(ServerGroup serverGroup);
/**
* Obtain LoadBalancer related Statistics
*/
public abstract LoadBalancerStats getLoadBalancerStats();
}
һ࣬һö٣chooseServer
ٿBaseLoadBalancer࣬BaseLoadBalancerǸؾһʵ࣬Կlist
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> allServerList = Collections
.synchronizedList(new ArrayList<Server>());
@Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> upServerList = Collections
.synchronizedList(new ArrayList<Server>());
Ͽάзʵбά״̬Ϊupʵб һԿBaseLoadBalancerʵֵILoadBalancerӿеķȡõķбͻupServerListأȡеķбͻallServerListء
@Override
public List<Server> getReachableServers() {
return Collections.unmodifiableList(upServerList);
}
@Override
public List<Server> getAllServers() {
return Collections.unmodifiableList(allServerList);
}
ٿDynamicServerListLoadBalancerࡣͷϵעͿ֪Զ̬ĻȡбfilterԷбйˡ
DynamicServerListLoadBalancerУܿһServerList͵serverListImplֶΣServerListһӿڣ
public interface ServerList<T extends Server> {
public List<T> getInitialListOfServers();
/**
* Return updated list of servers. This is called say every 30 secs
* (configurable) by the Loadbalancer's Ping cycle
*
*/
public List<T> getUpdatedListOfServers();
}
getInitialListOfServersǻȡʼķб getUpdatedListOfServersǻȡµķб ServerListжʵ࣬õĸأ EurekaRibbonClientConfigurationҵRibbonEurekaϵԶ࣬ĿǰûEurekaͨļãԻConfigurationBasedServerListࡣ
https://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF https://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA https://juejin.cn/post/6931922457741770760 https://github.com/D2C-Cai/herring http://c.biancheng.net/springcloud https://github.com/macrozheng/springcloud-learning