Code preparation
Dependency relationship
+------------+ +------------+ | | | | | | | | | | | | | | | | | consumer +------------> | provider | | | RestTemplate | | | | | | | | | | | | | | +------------+ +------------+
pom dependence
Just join the nacos service discovery, which internally references the spring cloud ribbon dependency
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
Call client
Let's start with the simplest RestTemplate call here
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } // Controller uses restTemplate to call service provider interface ResponseEntity<String> forEntity = restTemplate.getForEntity("http://provider/req", String.class);
Source code analysis
Create call interceptor
1. Get the RestTemplate of all @ LoadBalanced Tags
public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); }
2. Add LoadBalancerInterceptor processing logic
- Spring retry was not introduced
@Bean public LoadBalancerInterceptor ribbonInterceptor() { return new LoadBalancerInterceptor(); }
- Spring retry was introduced using
@Bean @ConditionalOnMissingBean public RetryLoadBalancerInterceptor ribbonInterceptor() { return new RetryLoadBalancerInterceptor(); }
- LoadBalancerInterceptor business logic
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept() { final URI originalUri = request.getURI(); // http://demo-provider/req intercept demo provider service name String serviceName = originalUri.getHost(); // Default injected RibbonAutoConfiguration.RibbonLoadBalancerClient return this.loadBalancer.execute(serviceName, // Create request object this.requestFactory.createRequest(request, body, execution)); } }
Execution interceptor
3. RibbonLoadBalancerClient execution
//RibbonLoadBalancerClient injected by default by RibbonAutoConfiguration @Bean @ConditionalOnMissingBean(LoadBalancerClient.class) public LoadBalancerClient loadBalancerClient() { return new RibbonLoadBalancerClient(springClientFactory()); }
4.execute
public class RibbonLoadBalancerClient implements LoadBalancerClient { public <T> T execute(){ //Get specific Iload balancer implementation ILoadBalancer loadBalancer = getLoadBalancer(serviceId); // Call ILoadBalancer to get Server Server server = getServer(loadBalancer, hint); RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); //Get the status recorder and save the selected server RibbonLoadBalancerContext context = this.clientFactory .getLoadBalancerContext(serviceId); RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server); T returnVal = request.apply(serviceInstance); statsRecorder.recordStats(returnVal); return returnVal; } }
Get ILoadBalancer
5 SpringClientFactory
// Implementation of generating LoadBalancer from bean factory protected ILoadBalancer getLoadBalancer(String serviceId) { return this.springClientFactory.getLoadBalancer(serviceId); } // For the specific generation logic, see RibbonClientConfiguration. This Bean will be created only when it is called by the factory @Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { return new ZoneAwareLoadBalancer<>(); }
6. Create the dependency elements of LoadBalancer
For the above default implementation, please refer to ribbonclientconfiguration. Zoneawareloadebalancer
Get service instance
//Server server = getserver (loadbalancer, hint); 4. Extract method protected Server getServer(ILoadBalancer loadBalancer, Object hint) { return loadBalancer.chooseServer(hint != null ? hint : "default"); }
7. ZoneAwareLoadBalancer
public class ZoneAwareLoadBalancer{ public ZoneAwareLoadBalancer() { // Call the parent class initialization method. The timing tasks for instance maintenance will be enabled here (refer to the extension for details) super(clientConfig, rule, ping, serverList, filter, serverListUpdater); } @Override public Server chooseServer(Object key) { // If the Nacos service used is found, there is no concept of Zone, and the implementation of the parent class is called directly if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) { return super.chooseServer(key); } // Here is the concept of Zone, for example Eureka (specific) ... return server; } }
- Parent class calls IRule to implement selection Server
public Server chooseServer(Object key) { return rule.choose(key); }
8.PredicateBasedRule selection rule
public abstract class PredicateBasedRule { @Override public Server choose(Object key) { ILoadBalancer lb = getLoadBalancer(); // Get assertion configuration Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key); if (server.isPresent()) { return server.get(); } else { return null; } } }
9. Zoneaavoidancepredicate service list assertion
public class ZoneAvoidancePredicate { @Override public boolean apply(@Nullable PredicateKey input) { if (!ENABLED.get()) { return true; } // Or get the region configuration. If Nacos is used, it directly returns true String serverZone = input.getServer().getZone(); if (serverZone == null) { // there is no zone information from the server, we do not want to filter // out this server return true; } // Regional high availability judgment ... } }
Extension: ServerList maintenance
Initialize ServerList
In 6. Above, create the dependency element of LoadBalancer, the instance instance table of ServerList target service, and the specific service discovery client implementation. Let's take a look at the implementation of Nacos
public class NacosServerList extends AbstractServerList<NacosServer> { @Override public List<NacosServer> getInitialListOfServers() { return getServers(); } @Override public List<NacosServer> getUpdatedListOfServers() { return getServers(); } private List<NacosServer> getServers() { String group = discoveryProperties.getGroup(); //Call Nacos SDK to query the instance list List<Instance> instances = discoveryProperties.namingServiceInstance() .selectInstances(serviceId, group, true); // Type conversion return instancesToServerList(instances); } }
Update ServerListUpdater
- Update operation after ServerList initialization through PollingServerListUpdater
public class PollingServerListUpdater implements ServerListUpdater { @Override public synchronized void start(final UpdateAction updateAction) { // Update task to updateAction final Runnable wrapperRunnable = () -> { updateAction.doUpdate(); lastUpdated = System.currentTimeMillis(); }; // Enable background thread to execute updateAction regularly scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay( wrapperRunnable, initialDelayMs, refreshIntervalMs, TimeUnit.MILLISECONDS ); } }
-
- updateAction implementation
public void doUpdate() { DynamicServerListLoadBalancer.this.updateListOfServers(); } Copy code
public class PollingServerListUpdater implements ServerListUpdater { public void updateListOfServers() { List<T> servers = new ArrayList(); // Call NacosServiceList to get the list of all services servers = this.serverListImpl.getUpdatedListOfServers(); // If the configuration instance filter is performing filtering if (this.filter != null) { servers = this.filter.getFilteredListOfServers((List)servers); } // Update LoadBalancer service list this.updateAllServerList((List)servers); } }
Extensions: Server state maintenance
- setupPingTask() is triggered when LoadBalancer is initially constructed
public BaseLoadBalancer() { this.name = DEFAULT_NAME; this.ping = null; setRule(DEFAULT_RULE); // Enable ping check task setupPingTask(); lbStats = new LoadBalancerStats(DEFAULT_NAME); }
- setupPingTask
void setupPingTask() { // Whether it is possible to ping. The default DummyPing directly skips not to execute if (canSkipPing()) { return; } // Execute PingTask lbTimer.schedule(new BaseLoadBalancer.PingTask(), 0, pingIntervalSeconds * 1000); // Open task new BaseLoadBalancer.Pinger(pingStrategy).runPinger(); }
- SerialPingStrategy serial execution logic
// Serial scheduling execution of Iping logic private static class SerialPingStrategy implements IPingStrategy { @Override public boolean[] pingServers(IPing ping, Server[] servers) { int numCandidates = servers.length; boolean[] results = new boolean[numCandidates]; for (int i = 0; i < numCandidates; i++) { results[i] = false; /* Default answer is DEAD. */ if (ping != null) { results[i] = ping.isAlive(servers[i]); } } return results; } }
- Call url to judge availability
public class PingUrl implements IPing { public boolean isAlive(Server server) { urlStr = urlStr + server.getId(); urlStr = urlStr + this.getPingAppendString(); boolean isAlive = false; HttpClient httpClient = new DefaultHttpClient(); HttpUriRequest getRequest = new HttpGet(urlStr); String content = null; HttpResponse response = httpClient.execute(getRequest); content = EntityUtils.toString(response.getEntity()); isAlive = response.getStatusLine().getStatusCode() == 200; return isAlive; } }
Extension: RibbonClient lazy load processing
It can be seen from the above that by default, the Ribbon will not create a LoadBalancer until the first request. This lazy loading mechanism will cause the delay of the first call to the service after the service is started, and even lead to the timeout fuse of the integrated circuit breaker (hystrix).
To solve this problem, we will configure the hungry loading of Ribbon
ribbon: eager-load: clients: - provider
- After the RibbonApplicationContextInitializer service is started, it automatically calls the factory to create the required ribbon clients in advance
public class RibbonApplicationContextInitializer implements ApplicationListener<ApplicationReadyEvent> { private final List<String> clientNames; protected void initialize() { if (clientNames != null) { for (String clientName : clientNames) { this.springClientFactory.getContext(clientName); } } } @Override public void onApplicationEvent(ApplicationReadyEvent event) { initialize(); } }
All of the above are my own thoughts. You are welcome to share them. By the way, please pay attention to them. Partners with ideas can comment or send me private letters~