专栏目录
- Spring Cloud OpenFeign 源码解析
- Spring Cloud Ribbon 源码解析
- Spring Cloud Alibaba Sentinel 源码解析
- Spring Cloud Gatway 源码解析
- Spring Cloud Alibaba Nacos 源码解析
代码准备
依赖关系
1+------------+ +------------+ 2| | | | 3| | | | 4| | | | 5| | | | 6| consumer +------------> | provider | 7| | RestTemplate | | 8| | | | 9| | | | 10| | | | 11+------------+ +------------+
pom 依赖
加入nacos 服务发现即可,内部引用了spring-cloud-ribbon相关依赖
1<dependency> 2 <groupid>com.alibaba.cloud</groupid> 3 <artifactid>spring-cloud-starter-alibaba-nacos-discovery</artifactid> 4</dependency>
调用客户端
我们这里以最简单的 RestTemplate 调用开始使用Ribbon
1@Bean 2@LoadBalanced 3public RestTemplate restTemplate() { 4 return new RestTemplate(); 5} 6 7// Controller 使用restTemplate 调用服务提供方接口 8ResponseEntity<string> forEntity = restTemplate.getForEntity("http://provider/req", String.class); 9
源码解析
创建调用拦截器
1. 获取全部 @LoadBalanced标记的RestTemplate
1public class LoadBalancerAutoConfiguration { 2 @LoadBalanced 3 @Autowired(required = false) 4 private List<resttemplate> restTemplates = Collections.emptyList(); 5}
2. 增加 LoadBalancerInterceptor 处理逻辑
-
没有引入
spring-retry使用的是@Bean public LoadBalancerInterceptor ribbonInterceptor() { return new LoadBalancerInterceptor(); }
-
引入
spring-retry使用的是@Bean @ConditionalOnMissingBean public RetryLoadBalancerInterceptor ribbonInterceptor() { return new RetryLoadBalancerInterceptor(); }
-
LoadBalancerInterceptor 业务逻辑
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept() { final URI originalUri = request.getURI(); // http://demo-provider/req 截取 demo-provider 服务名称 String serviceName = originalUri.getHost();
1 // 默认注入的 RibbonAutoConfiguration.RibbonLoadBalancerClient 2 return this.loadBalancer.execute(serviceName, 3 // 创建请求对象 4 this.requestFactory.createRequest(request, body, execution)); 5}}
执行拦截器
3. RibbonLoadBalancerClient执行
1//RibbonAutoConfiguration默认注入的RibbonLoadBalancerClient 2@Bean 3@ConditionalOnMissingBean(LoadBalancerClient.class) 4public LoadBalancerClient loadBalancerClient() { 5 return new RibbonLoadBalancerClient(springClientFactory()); 6}
4.execute执行
1public class RibbonLoadBalancerClient implements LoadBalancerClient { 2 public <t> T execute(){ 3 //获取具体的ILoadBalancer实现 4 ILoadBalancer loadBalancer = getLoadBalancer(serviceId); 5 6 // 调用ILoadBalancer 实现获取Server 7 Server server = getServer(loadBalancer, hint); 8 RibbonServer ribbonServer = new RibbonServer(serviceId, server, 9 isSecure(server, serviceId), 10 serverIntrospector(serviceId).getMetadata(server)); 11 12 //获取状态记录器,保存此次选取的server 13 RibbonLoadBalancerContext context = this.clientFactory 14 .getLoadBalancerContext(serviceId); 15 RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server); 16 T returnVal = request.apply(serviceInstance); 17 statsRecorder.recordStats(returnVal); 18 return returnVal; 19 } 20}
获取ILoadBalancer
5 SpringClientFactory
1// bean 工厂生成LoadBalancer 的实现 2protected ILoadBalancer getLoadBalancer(String serviceId) { 3 return this.springClientFactory.getLoadBalancer(serviceId); 4} 5 6// 具体生成逻辑看 RibbonClientConfiguration,这个Bean 只有工厂调用的时候才会创建 7@Bean 8@ConditionalOnMissingBean 9public ILoadBalancer ribbonLoadBalancer(IClientConfig config, 10ServerList<server> serverList, ServerListFilter<server> serverListFilter, 11IRule rule, IPing ping, ServerListUpdater serverListUpdater) { 12 return new ZoneAwareLoadBalancer<>(); 13}
6.创建LoadBalancer 的依赖要素
名称
默认实现
作用
IClientConfig
DefaultClientConfigImpl
ribbon 客户端配置参数,例如: 超时设置、压缩设置等
ServerList
NacosServerList
目标服务的实例实例表,具体服务发现客户端实现
ServerListFilter
ZonePreferenceServerListFilter
针对ServerList 实例列表的过滤逻辑处理
IRule
ZoneAvoidanceRule
负载均衡选择Server 的规则
IPing
DummyPing
检验服务是否可用的方法实现
ServerListUpdater
PollingServerListUpdater
针对ServerList 更新的操作实现
以上默认实现参考 RibbonClientConfiguration. ZoneAwareLoadBalancer
获取服务实例
1//Server server = getServer(loadBalancer, hint); 4. excute 方法 2protected Server getServer(ILoadBalancer loadBalancer, Object hint) { 3 return loadBalancer.chooseServer(hint != null ? hint : "default"); 4}
7. ZoneAwareLoadBalancer
1public class ZoneAwareLoadBalancer{ 2 public ZoneAwareLoadBalancer() { 3 // 调用父类初始化方法。 这里会开启实例维护的定时任务等 (具体解析参考 扩展部分) 4 super(clientConfig, rule, ping, serverList, filter, serverListUpdater); 5 } 6 @Override 7 public Server chooseServer(Object key) { 8 // 若是使用的 Nacos 服务发现,则没有 Zone 的概念,直接调用父类的实现 9 if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) { 10 return super.chooseServer(key); 11 } 12 // 以下为有 Zone 的概念 例如 Eureka (具体) 13 ... 14 return server; 15 } 16}
-
父类调用
IRule实现选择Serverpublic Server chooseServer(Object key) { return rule.choose(key); }
8.PredicateBasedRule 选择规则
1public abstract class PredicateBasedRule { 2 @Override 3 public Server choose(Object key) { 4 ILoadBalancer lb = getLoadBalancer(); 5 // 获取断言配置 6 Optional<server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key); 7 if (server.isPresent()) { 8 return server.get(); 9 } else { 10 return null; 11 } 12 } 13}
9. ZoneAvoidancePredicate服务列表断言
1public class ZoneAvoidancePredicate { 2 @Override 3 public boolean apply(@Nullable PredicateKey input) { 4 if (!ENABLED.get()) { 5 return true; 6 } 7 // 还是获取区域配置,如是使用的 Nacos 直接返回true 8 String serverZone = input.getServer().getZone(); 9 if (serverZone == null) { 10 // there is no zone information from the server, we do not want to filter 11 // out this server 12 return true; 13 } 14 // 区域高可用判断 15 ... 16 } 17}
扩展: ServerList 维护
初始化ServerList
在上文 6.创建LoadBalancer 的依赖要素,中 ServerList 目标服务的实例实例表,具体服务发现客户端实现。我们来看下 Nacos 的实现
1public class NacosServerList extends AbstractServerList<nacosserver> { 2 @Override 3 public List<nacosserver> getInitialListOfServers() { 4 return getServers(); 5 } 6 7 @Override 8 public List<nacosserver> getUpdatedListOfServers() { 9 return getServers(); 10 } 11 12 private List<nacosserver> getServers() { 13 String group = discoveryProperties.getGroup(); 14 //调用nacos-sdk 查询实例列表 15 List<instance> instances = discoveryProperties.namingServiceInstance() 16 .selectInstances(serviceId, group, true); 17 // 类型转换 18 return instancesToServerList(instances); 19 20 } 21}
更新ServerListUpdater
-
ServerList 初始化后更新操作通过
PollingServerListUpdaterpublic class PollingServerListUpdater implements ServerListUpdater { @Override public synchronized void start(final UpdateAction updateAction) { // 更新任务 交给updateAction 具体实现 final Runnable wrapperRunnable = () -> { updateAction.doUpdate(); lastUpdated = System.currentTimeMillis(); };
1 // 开启后台线程定时执行 updateAction 2 scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay( 3 wrapperRunnable, 4 initialDelayMs, 5 refreshIntervalMs, 6 TimeUnit.MILLISECONDS 7 ); 8}}
-
updateAction 实现
public void doUpdate() { DynamicServerListLoadBalancer.this.updateListOfServers(); }
public class PollingServerListUpdater implements ServerListUpdater { public void updateListOfServers() { List<t> servers = new ArrayList();
1 // 调用NacosServiceList 获取全部服务列表 2 servers = this.serverListImpl.getUpdatedListOfServers(); 3 4 // 如果配置实例过滤器在执行过滤 5 if (this.filter != null) { 6 servers = this.filter.getFilteredListOfServers((List)servers); 7 } 8 9 // 更新LoadBalancer 服务列表 10 this.updateAllServerList((List)servers); 11}}
扩展: Server 状态维护
-
LoadBalancer 初始构造时会触发
setupPingTask()public BaseLoadBalancer() { this.name = DEFAULT_NAME; this.ping = null; setRule(DEFAULT_RULE); // 开启ping 检查任务 setupPingTask(); lbStats = new LoadBalancerStats(DEFAULT_NAME); }
-
setupPingTask
void setupPingTask() { // 是否可以ping, 默认的DummyPing 直接 跳过不执行 if (canSkipPing()) { return; } // 执行PingTask lbTimer.schedule(new BaseLoadBalancer.PingTask(), 0, pingIntervalSeconds * 1000); // 开启任务 new BaseLoadBalancer.Pinger(pingStrategy).runPinger(); }
-
SerialPingStrategy 串行执行逻辑
// 串行调度执行 Iping 逻辑 private static class SerialPingStrategy implements IPingStrategy { @Override public boolean[] pingServers(IPing ping, Server[] servers) { int numCandidates = servers.length; boolean[] results = new boolean[numCandidates];
1for (int i = 0; i < numCandidates; i++) { 2 results[i] = false; /* Default answer is DEAD. */ 3 if (ping != null) { 4 results[i] = ping.isAlive(servers[i]); 5 } 6 7} 8return results;} }
-
调用url 判断可用性
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;
1 HttpResponse response = httpClient.execute(getRequest); 2 content = EntityUtils.toString(response.getEntity()); 3 isAlive = response.getStatusLine().getStatusCode() == 200; 4 return isAlive; 5}}
扩展: RibbonClient 懒加载处理
由上文可知,默认情况下 Ribbon 在第一次请求才会去创建 LoadBalancer ,这种懒加载机制会导致服务启动后,第一次调用服务延迟问题,甚至在整合 断路器(hystrix)等出现超时熔断 。
为了解决这个问题,我们会配置 Ribbon 的饥饿加载
1ribbon: 2 eager-load: 3 clients: 4 - provider
-
RibbonApplicationContextInitializer服务启动后自动调用 工厂提前创建需要的ribbon clientspublic class RibbonApplicationContextInitializer implements ApplicationListener<applicationreadyevent> { private final List<string> clientNames;
1protected void initialize() { 2 if (clientNames != null) { 3 for (String clientName : clientNames) { 4 this.springClientFactory.getContext(clientName); 5 } 6 } 7} 8@Override 9public void onApplicationEvent(ApplicationReadyEvent event) { 10 initialize(); 11}}
后续计划
欢迎关注我,后边更新 Ribbon、Hystrix、Sentinel、Nacos 等组件源码图文解析。
另注: 以上图片素材 (omnigraffle & 亿图) 可以在公众号 JAVA架构日记 获取
『★★★★★』 基于Spring Boot 2.2、 Spring Cloud Hoxton & Alibaba、 OAuth2 的RBAC 权限管理系统 > 项目推荐: Spring Cloud 、Spring Security OAuth2的RBAC权限管理系统 欢迎关注 </string></applicationreadyevent></t></instance></nacosserver></nacosserver></nacosserver></nacosserver></server></server></server></t></resttemplate></string>