-1- 负载均衡是对系统的高可用、网络压力缓解和处理能力扩容的重要手段。
-2- 利用特定方式将流量分摊到多个操作单元手段。
-3- 通常所说的负载均衡都指的是=服务端负载均衡=,如Nginx(软件负载均衡)与F5(硬件负载均衡)。
-4- 服务端负载均衡(集中式负载均衡)Nginx 与 客户端负载均衡(进程内负载均衡)Ribbon。
-5- 此为,Feign(服务间调用)与Zuul(网关)组件中已经默认整合集成了Ribbon。
- 集中式负载均衡(服务端负载均衡)-需要单独部署服务器 如:Nginx :
- 客户端负载均衡(进程内负载均衡如)-不需要单独部署服务器,整合使用服务器即可:Ribbon :
-1- 客户端负载均衡(进程内负载均衡如)是指:从一个实例库选取一个实例进行流量导入,
-2- 实例库一般是存储在 Eureka、Consul、Zookeeper 等 注册中心,
-3- 而此时的负载均衡器就是类似Ribbon的IPC(Inter-Process Communication) 进程间通信。
-上面👆图多看多理解-
1 > Ribbon 入门案例
<!-- Eureka-Server 服务依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<!-- Eureka-Server 主程序Main类-->
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
<!-- Eureka-Server application.yml 配置文件-->
server:
port: 8888
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
> Eureka-Client-R 服务,是为了测试Ribbon负载均衡功能的定义的:-源服务器。
<!-- Eureka-Client-R 服务依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<!-- Eureka-Client-R 主程序Main类-->
@SpringBootApplication
@EnableDiscoveryClient <!-- -->
<!-- 注意:这里使用的并不是 @EnableEurekaClient注解 -->
public class ClientAApplication {
public static void main(String[] args) {
SpringApplication.run(ClientAApplication.class, args);
}
}
**@EnableEurekaClient与@EnableDiscoveryClient区别 **1
<!-- Eureka-Client-R application.yuml -->
server:
port: 7070
spring:
application:
name: Client-R
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8888}/eureka/
instance:
prefer-ip-address: true
<!-- Eureka-Client-R Controller 层,用于测试,在该API中除了计算结果之外,
还携带当前实例的端口号 -->
@RestController
public class TestController {
@GetMapping("/add")
public String add(Integer a, Integer b, HttpServletRequest request){
return " From Port: "+ request.getServerPort() + ", Result: " + (a + b);
}
}
> Ribbon-loadbalancer (负载均衡器)Client 客户端服务
<!-- Ribbon-loadbalancer (负载均衡器)Client 客户端 依赖包 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<!-- Ribbon-loadbalancer (负载均衡器)Client 客户端 application.yml -->
spring:
application:
name: ribbon-loadbalancer
server:
port: 7777
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8888}/eureka/
instance:
prefer-ip-address: true
<!-- Ribbon-loadbalancer (负载均衡器)Client 客户端 主程序Main入口 -->
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonLoadbalancerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonLoadbalancerApplication.class, args);
}
@Bean <!--注入RestTemplate(模版)的Bean -->
@LoadBalanced <!--该注解声明该RestTemplate用于负载均衡 -->
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
<!-- Ribbon 客户端需要创建一个API来调用Client-R 源服务的那个自定义AIP
这里需要用到RestTemplate来调用,http后面的IP:port变成具体的服务名 如下: -->
@RestController
public class TestController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/add")
public String add(Integer a, Integer b) {
String result = restTemplate
.getForObject("http://CLIENT-R/add?a=" + a + "&b=" + b, String.class);
System.out.println(result);
return result;
}
}
-上面👆操作完整后,开始测试:-
1)启动 Eureka Server之后,修改Client-R服务的端口号为7070与7075分别启动。
2)访问http://localhost:8888 可以清晰看到已经启动来两个为Client-R的服务实例。UP(2)
3)访问http:localhost:7777/add?a=100 &b=300,会有两种结果
4)From Prot:7075,Result:400
5)From Prot: 7070,Result :400
6) 由于我们程序中也实现打印调用的结果,控制台打印信息如 上面👆交错结果集。
7)Ribbon默认使用轮询的方式访问-源服务。
2 > Ribbon负载均衡策略与自定义配置
– 先说说别人家的孩子:Nginx 呵-
– Nginx常用的负载均衡策略:
– 轮询(Round Robin)策略
– 权重(Weight)策略
– IP—Hash 策略 等-
表2-1 Ribbon 负载均衡策略:
策略类 | 命名 | 描述 |
---|---|---|
RandomRule | 随机策略 | 随机选择 Sever 服务器 |
RoundRobinRule | 轮询策略(Ribbon默认策略)[BaseLoadBalancer类中默认设置的是RoundPobinRule策略] | 按顺序循环♻️选择 Server |
RetryRule | 重试策略 | 在一个配置时间端内当选择Server不成功,则一直尝试选择一个可用的Server |
BestAvailableRule | 最低并发策略 | 逐个考察Server,如果Server断路器打开,则忽略,再选择其中并发连接最低的Server |
AvailabilityFilteringRule | 可用过滤策略 | 过滤掉一直连接失败并被标记为Circuit(环形) Tripped(旅行)的Server,过滤掉那些并高并发连接的Server(Active(活跃)Connections 超过配置的阀值) |
ResponseTimeWeigHtedRule | 响应时间家权策略 | 根据Server的响应时间分配权重。响应时间越长,权重越低,被选择到的概率就越低,响应时间越短,权重越高,被选择到的概率就越高,这个策略很贴切,综合类各种因素,如:网络、磁盘、IO等,这些因素直接影响者响应时间。 |
ZoneAvoidanceRule | 区域权衡策略 | 综合判断Server所在区域的性能和Server的可用性轮询选择Server,并且判定一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有Sever. |
> 1.全局更改Ribbon负载均衡默认策略-配置类
<!-- 如此简单的一个简单的配置,加上以后凡是通过Ribbon的请求都会按照配置的规则来进行-->
@Configuration
public class TestConfiguration {
@Bean
public IRule ribbonRule() {
return new RandomRule();
<!-- 从默认轮询策略-RoundRobinRule 更改成 -随机策略-RandomRule-->
}
}
> 2.基于注解的策略-配置
<!-- 针对-源服务器设置其特定的策略=可以使用用@RibbnClient注解 如下:-->
@Configuration
@AvoidScan<!--这里的@AvoidScan注解是一个空的声明-->
public class TestConfiguration {
@Autowired
IClientConfig config;<!--注入的IClientConfi是针对客户端的配置管理器-->
@Bean
public IRule ribbonRule(IClientConfig config) {
return new RandomRule();
}
<!-- 最后在启动类上加上@RibbonClient注解-来对源服务进行负载约束。-->
}
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "client-r", configuration = TestConfiguration.class)
<!-- 此外,可以使用@RibbonClients注解来对多个源服务进行策略指定-->
@RibbonClients(value = {
@RibbonClient(name = "client-a", configuration = TestConfiguration.class),
@RibbonClient(name = "client-b", configuration = TestConfiguration.class)
})
@ComponentScan(excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {AvoidScan.class})})
<!--这里使用@ComponentScan注解的意思是让Sping不去扫描被@avoidScan注解标记的配置类
因为我们的配置是对单个源服务生效的,所有不能应用与全局,如果不排除,启动就会报错❌
-->
public class RibbonLoadbalancerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonLoadbalancerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
> 3.基于文件策略-配置
client-r:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule#
> 4.Ribbon超时与重试
<!-- 使用HTTP发起请求,免不了要经历极端环境,此时调用进行时限控制以及时限之后的重试尤为重要
需要添加对于超时时间与重试策略的配置-如下:-->
client-r:
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
MaxAutoRetries: 1 #对第一次请求的服务的重试次数
MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
OkToRetryOnAllOperations: true
> 4.Ribbon饥饿加载
<!-- Ribbon在进行客户端负载均衡的时候并不是在启动时加载上下文,而是在实际请求的时候才去创建,
因此这个特性汪汪会让我们第一此调用显得颇为疲软乏力,严重的时候会引起调用超时。
所以可通过指定Ribbon具体的客户端的名称来开启饥饿加载,即在启动过的时候便加载
所以配置项的应用程序上下文,如下:
-->
eager-load:
enabled: true
clients: client-r, client-b, client-c
> 5.利用配置文件自定义Ribbon客户端
自定义定制 Ribbon客户端,其实就是使用配置文件来指定一个默认加载类,
从而该改变Ribbon客户端的行为方式,并且使用这种方式优先级最好,优先级高于使用
注解@RibbonClent的指定的配置和源码中加载的相关Bean。
-5-1表.配置文件自定义Ribbon客户端的语法说明:
配置项 | 说明 |
---|---|
< clientNmae >. ribbon.NFLoadBalancerClassName | 指定ILoadBalancer(I负载均衡器)的实现类 |
< clientNmae >. ribbon.NFLoadBalancerRuleClassName | 指定IRule(I规则)的实现类 |
< clientNmae >. ribbon.NFLoadBalancerPingClassName | 指定IPing(I抨击)的实现类 |
< clientNmae >. ribbon.NFNIWSSercerListClassName | 指定SercerList(I服务列表)的实现类 |
< clientNmae >. ribbon.NFNIWSSercerListFilterClassName | 指定SercerListFilter(I服务列表过滤器)的实现类 |
<!-- 使用Ribbon自带实现类,也可以自实现-如下:-->
client-r:
ribbon:
NiwSServerListClassName: com.netfix.loadbalancer.ConfigurationBasedServerList
NFLoadBalancerRuleClassName: com.net.loadbaleancer.WeightedReponseTimeRule
> 6.Ribbon脱离Eureka使用:
1-默认情况下,Ribbon客户端会从Eureka注册中心读取服务注册信息列表,来达到动态负载均衡的功能。
2-如果Eureka是一个供跟多人使用的公共注册中心(比如:http://eureka.springcloud.cn),
3-那么此时极易产生服务侵入型问题,所以就不要从Eureka中读取服务列表来,
4-而应该在Ribbon客户端自行指定-源服务-地址,让Ribbon脱离Eureka来使用。
<!--为了达到这一目的,首先需要在Ribbon中禁用Eureka的功能-->
ribbon:
eureka:
enabled:false
<!--然后对源服务设定地址列表:-->
client:
ribbon:
listOPfServers: http://localhost:7070,http://localHost:7071
> 7.Ribbon核心工作原理:
奕小生,重点分享/讲解官方文档中提到的那些上层接口及Ribbon从启动到负载均衡器选择服务实例的过程
> 7-1.官方文档中提到Ribbon核心接口:
接口 | 描述 | 默认实现类 |
---|---|---|
IClientConfig | 定义Ribbon中管理配置的接口 | DefaultCientConfigImpl |
IRule | 定义Ribbon中负载均衡策略的接口 | ZoneAvidanceRule(区证明规则) |
IPing | 定义定期Ping服务检查可用性的接口 | DummyPing(假抨击) |
ServerList< Server > | 定义获取服务列表方法的接口 | ConfigurationBasedServerLIit(构造基于服务列表) |
ServerListFilter< Server > | 定义特定期望获取服务列表方法的接口 | ZonePreferenceServerListFilter(区优先权服务列表过滤器) |
ILoadBalancer | 定义负载均衡选择服务的核心方法的接口 | ZoneAwareLoadBalancer(区自觉加载负载均衡) |
ServerListUpdater | 为Dynamic(动态/活跃)ServerListLoadBalacer定义动态更新服务列表的接口 | PoliingServerListUpdater(警务服务列表更新) |
== > 最关心疑问🤔️:Ribbon如何做到/实现-使用RestTemplater达到负载均衡的 ?< ==
<!--1、使用Ribbon来对客户端实例进行负载均衡-->
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonLoadbalancerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonLoadbalancerApplication.class, args);
}
@Bean<!--2、基本使用方式都要注入一个RestTemplate的Baen-->
@LoadBalanced <!--3、并且使用@LoadBalanced注解才能具备负载均衡能力-->
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
<!--1、使用Ribbon来对客户端实例进行负载均衡-->
<!--2、基本使用方式都要注入一个RestTemplate的Baen-->
<!--3、并且使用@LoadBalanced注解才能具备负载均衡能力-->
-- 👇下面👇是:@LoadBalanced注解里的源码:
/** 源码
* Annotation to mark a RestTemplate bean to be configured to use a
* LoadBalancerClient (译:将RestTemplate bean配置为使用 LoadBalancerClient)
== issue(疑问🤔️):>那么问题又来了,LoadBalancerClient 是什么 ?看下面源码👇👇👇< ==
* @author Spencer Gibb -- @作者 斯宾塞 吉布
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
====================================================================================
<!--LoadBalancerClienta(负载均衡客户端)接口扩展自:
ServiceInstanceChooser(服务实例选项器)接口-->
/** 源码
* Represents a client side load balancer(译:代表客户端负载均衡器)
* @author Spencer Gibb -- @作者 斯宾塞 吉布
*/
public interface LoadBalancerClient extends ServiceInstanceChooser {
<!--源文注释的意思:使用来自LoadBalancer(负载均器)的ServiceInstance(服务实例)为指定的服务执行请求。-->
<T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException;
<!--源文注释的意思:使用来自LoadBalancer(负载均器)的ServiceInstance(服务实例)
为指定的服务执行请求,是上一个方法的重载,在实现类中可以看到它们的关系,其中就是前一个方法的细节实现-->
<T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException;
<!--源文注释的意思:使用主机IP和port(端口)构建特定的URL供Ribbon内部使用。Ribbon使用
具有逻辑服务名称的URL作为host,例如:http//myserverce/path/to/service-->
URI reconstructURI(ServiceInstance instance, URI original);
}
--------------------------------------------------------------------------------
/** 源码*/
public interface ServiceInstanceChooser {
<!--源文注释的意思:根据Serverid结合负载均衡器选择一个服务实例-->
ServiceInstance choose(String serviceId);
}
Ribbon被初始化的类,同包下LoadBalancerAutoConfiguration(负载均衡器自动配置类)
-Ribbon功能核心配置类:
package org.springframework.cloud.client.loadbalancer;
/** 源码
* Auto configuration for Ribbon (client side load balancing).
* (译:自动配置 Ribbon - 客户端负载平衡)
*
* @author Spencer Gibb -- @作者 斯宾塞 吉布
* @author Dave Syer
* @author Will Tran
* @author Gang Li
*/
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
/** 从类的注解可以看出,它配置加载的时机:
* 1.当前工程环境必须有RestTemplater的实例,
* 2.在工程环境中必须初始化类 LoadBalancerClient(负载均衡器客户端) 的实现类,
* 3.其中 LoadBalancerRequestFactory(负载均衡器请求工厂)用于,
* 创建 LoadBalancerRequest(负载均衡器请求)供 LoadBalancer(负载均衡器)-Interceptor(拦截器)使用.
* 4.loadBalancerInterceptorConfig(负载均衡器拦截器配置)内部类中则:
* 维护了LoadBalancerInterceptor(负载平衡器拦截器) 与 RestTemplateCustomizer(Rest模版定制器)的实例.
* 5.LoadBalancerInterceptor 作用:拦截每一次HTTP请求,将请求绑定进Ribbon负载均衡的生命周期。
* 6.RestTemplateCustomizer 作用:为每一个 RestTemplate 绑定 LoadBalancerInterceptor拦截器。
*/
--接下来我们瞅瞅 LoadBalancerInterceptor(负载平衡器拦截器) 类里面做了什么 ? 往下翻看啦~--
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {};
}
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
==================================================================================================
--接下来我们瞅瞅 LoadBalancerInterceptor(负载平衡器拦截器) 类 源码~--
package org.springframework.cloud.client.loadbalancer;
/**
* @author Spencer Gibb -- @作者 斯宾塞 吉布
* @author Dave Syer
* @author Ryan Baxter
* @author William Tran
*/
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
/**
* Class 的实现上利用了 ClientHttpRequestInterceptor-客户端Http请求拦截器 来对每次HTTP请求进行拦截的,
* 此类是Spirng中维护的请求拦截器,实现它的 intercept()-拦截-方法就可以使请求进入方法体,从而做一些处理。
* 请求拦截下来之后使用了LoadBalancerClient-负载均衡器客户端-的 execute()-执行-方法来处理请求
* LoadBalancerClient接口只有一个实现类[IDEA编辑器中Ctrl(Command) + H -查看接口实现类列表],
* 它就是RibbonLoadBalancerClient(Ribbon负载均衡器客户端)。
*/
--再接下来我们瞅瞅 RibbonLoadBalancerClient(Ribbon负载均衡器客户端) 类 再往下翻~~--
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));
}
}
===========================================================================================
--瞅瞅 RibbonLoadBalancerClient(Ribbon负载均衡器客户端) 类 源码~ 直接找到execute()-执行-方法--
package org.springframework.cloud.netflix.ribbon;
/**
* @author Spencer Gibb -- @作者 斯宾塞 吉布
* @author Dave Syer
* @author Ryan Baxter
* @author Tim Ysewyn
*/
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
..................
/** execute()-执行-方法,它首先要得到一个ILoadBalancer-I负载均衡器 接口,再使用它去得到一个Server,
* 这里Server就是具体服务实例的封装类,所以,getServer(loadBalancer)就是发生负载均衡过程的地方!
* 往下翻,一起儿瞅瞅getServer() 做了什么?--->
*/
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
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));
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;
}
.............................
/** 接着看 getServer()方法的内容:
* 它运用了ILoadBalancer接口的chooseServer()方法
*/
-------------------------------------------------------------------------
protected Server getServer(String serviceId) {
return getServer(getLoadBalancer(serviceId));
}
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
-------------------------------------------------------------------------
/** 紧追 chooseServer()实现类
* ILoadBalancer接口唯一实现类是:AbstractLoadBalancer类但它是个抽象类因此没有方法体,
* 继承抽象类的是: BaseLoadBalancer类,chooseServer()方法的具体实现就它这个类中编写的;
*/
-------------------------------------------------------------------------
<!-- 直接给‘码’同志们瞅 BaseLoadBalancer类中的 chooseServer()方法的具体实现-->
package com.netflix.loadbalancer;
...................
/* 源码
* Get the alive server dedicated to key(译:获取专用于密钥的在线服务器)
*
* @return the dedicated server(译:@返回 这个专用服务器)
*/
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
// ‘这步骤操作后,发现rule.choose(key)其实就是IRule(IRule是定义Ribbon负载均衡策略的父接口,
// 所有策略都要基于它实现的) 至此,拦截的HTTP请求与负载均衡策略得以关联起来嘀!’
-------------------------------------------------------------------------
--进来瞅瞅父接口IRule接口--
package com.netflix.loadbalancer;
public interface IRule{
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
/** IRule接口一共定义类三个方法:
* 1.实现类实现choose()方法会加入具体的负载均衡策略逻辑,
* 2.另外两个方法与ILoadBalancer关联起来,
* 3.在调用Ribbon是通过ILoadBalacer来关联IRule的,
* 4.ILoadBalancer的chooseServer()方法会转换为调用IRule的choose()方法,
* 5.抽象类 AbstractLoadBalancerRule实现类这两个方法,
* 6.从而将ILoadBalancer与IRule关联起来,
* 7.再往下走就是实现类了,每一种实现逻辑需要慢慢看实现类喽!
*/
}
-------------------------------------------------------------------------
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
........................
-------------------------------------------------------------------------
== 《Ribbon 负载均衡家族体系图 》==
小结一下 喽喽-:‘码’软文软的冒汗~虚了虚了~枸杞枸杞,反手一个赞👍 嘻嘻~
-
- @EnableEurekaClient注解是基于spring-cloud-netflix依赖,只能为eureka作用,
– @EnableDiscoveryClient注解是基于spring-cloud-commons依赖,并且在classpath中实现;
– 1、两者的作用范围差异,前者单一,后者比前者对于Eureka注册中心相对选择注册中心多选项的,
– 2、@EnableDiscoveryClient 关键词 Discovery 发现机制。
– 3、在该项目中使用的目的:
– 4、启动类上表示 该服务被注册中心发现即可,
– 5、.yml 文件中指定注册中心地址与初始的端口,后面开始多个实例只需要修改端口即可。
– 6、如直接使用 @EnableEurekaClient 注解,则达不到目的。
- @EnableEurekaClient注解是基于spring-cloud-netflix依赖,只能为eureka作用,