1.服务器端负载均衡:
nginx 是部署在服务器端的
2. 客户端负载均衡:
内容中心相对于用户中心 ,是客户端

手写一个客户端负载均衡器:
就是从一个list集合中随机选取一个元素作为请求实例
3.使用Ribbon 实现负载均衡:
1.不需要在pom中添加依赖
2. 添加注解:(在RestTemplate 上)
@LoadBalanced
代码:【现在的代码】
不需要拿到用户中心所有实例的信息,在从中选取一个实例
【原来的代码】
当restTemplate 去请求时,ribbon 会自动将user-center 转换成用户中心在 Nacos上的地址,并且对服务端(用户中心)进行负载均衡算法,计算出一个实例进行请求
4.Ribbon内置的负载均衡规则
5.ribbon 细粒度配置自定义:
1.Java代码配置:
1.在当前启动类所在包,及所在包下的其他的包 之外 ,创建一个配置类:(配置类所在的包也要自己创建)
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
}
这里使用的是RandomRule,随机选择一个server
2. 创建一个 UserCenterConfiguration 类
@Configuration
@RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
public class UserCenterConfiguration {
}
注意: configuration = RibbonConfiguration.class
这样就使ribbon的负载均衡规则变成了 :随机选取。
==================================
为什么 RibbonConfiguration 要在启动类所在包之外?
答: RibbonConfiguration 的@Configuration注解 是一个特殊的@Component,
而在启动类的@SpringBootApplication注解内 有个@ComponentScan 注解(会扫描 启动类所在包,及旗下包的 @Component注解,),若ribbon的配置类若在 启动类包下,则一定会被扫描到【但是ribbon的配置类一定不能被扫描到】。
@SpringBootApplication扫描的上下文叫 主上下文, ribbon的是 子上下文【注意:父子上下文扫描的包一定不能重叠,否则会出现各种问题,例如:spring容器中配置的事务失效】
RibbonConfiguration 类一定要有 @Configuration 注解,但不能被 @SpringBootApplication扫描到,否则 会被所有的@RibbonClient 共享【这里是要对 ribbon 进行细粒度配置,即:客户端 对A服务选择轮训,对 B服务选择随机等等 】
=====================================
2.使用配置属性配置:
在 application.yml 中进行配置:
是对 user-center 这个服务进行配置
com.netflix.loadbalancer.RandomRule 是 RandomRule 的全路径地址
3.对比两种配置方式:
所以:尽量使用属性配置,在同一个服务内 尽量 保持单一性,比如:统一使用 属性配置,不要两种方式混用,增加定位代码的复杂性
6. ribbon 的全局配置:
还是使用Java代码配置:
1.创建一个RibbonConfiguration 类
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
}
2.创建一个
UserCenterConfiguration类【叫什么名字无所谓(沿用上文而已),也可以是Configuration】
注意:这里 是 @RibbonClients 和defaultConfiguration ,没有了 name
7.ribbon的饥饿加载:
例如:
我们在使用Spring Cloud的Ribbon或Feign来实现服务调用的时候,如果我们的机器或网络环境等原因不是很好的话,有时候会发现这样一个问题:我们服务消费方调用服务提供方接口的时候,第一次请求经常会超时,而之后的调用就没有问题了。下面我们就来说说造成这个问题的原因,以及如何解决的方法。
答:造成第一次服务调用出现失败的原因主要是Ribbon进行客户端负载均衡的Client并不是在服务启动的时候就初始化好的,而是在调用的时候才会去创建相应的Client,所以第一次调用的耗时不仅仅包含发送HTTP请求的时间,还包含了创建RibbonClient的时间,这样一来如果创建时间速度较慢,同时设置的超时时间又比较短的话,很容易就会出现上面所描述的显现。
可以通过饥饿加载来解决:
在yml中:
clients :指定 对 哪个 服务进行 加载 ,多个 用 逗号 隔开 ,不在 clients 内的还是使用懒加载模式
8.扩展Ribbon-权重支持 :
在nacos 控制台可以为每一个实例配置权重.
权重值越大,被调用的几率越大。
把性能差的机器权重设低,性能好的机器权重设高,让请求优先打到性能高的机器上去;
但是ribbon的负载均衡规则 都不支持 Nacos的权重,所以自定义 一个负载均衡规则,支持权重。
1.创建一个NacosWeightedRule类:
@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
//读取配置文件,并初始化NacosWeightedRule
}
@Override
public Server choose(Object o) {
try {
BaseLoadBalancer loadBalancer=(BaseLoadBalancer) this.getLoadBalancer();
//想要请求的微服务的名称
String name=loadBalancer.getName();
//实现负载均衡算法,nacos 已经内置了算法
//拿到服务发现的相关api
NamingService namingService =nacosDiscoveryProperties.namingServiceInstance();
//nacos client 自动通过 基于权重的负载均衡算法,给我们选择一个实例
Instance instance= namingService.selectOneHealthyInstance(name);
log.info("选择的实例是:port={}, instance={}",instance.getPort(),instance);
return new NacosServer(instance);
} catch (NacosException e) {
return null ;
}
}
}
注意:initWithNiwsConfig方法是用不到的,所以置空,只使用了choose 方法,调用了nacos的基于权重的负载均衡算法
2.在RibbonConfiguration类中new 出来:
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new NacosWeightedRule();
}
}
3.使用全局配置:
@Configuration
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)
public class UserCenterConfiguration {
}
既然Nacos Client已经有负载均衡的能力,Spring Cloud Alibaba为什么还要去整合Ribbon呢?
答:
这主要是为了符合Spring Cloud标准。Spring Cloud Commons有个子项目 spring-cloud-loadbalancer
,该项目制定了标准,用来适配各种客户端负载均衡器(虽然目前实现只有Ribbon,但Hoxton就会有替代的实现了)【但没有基于权重的规则】。
Spring Cloud Alibaba遵循了这一标准,所以整合了Ribbon,而没有去使用Nacos Client提供的负载均衡能力。
9.扩展Ribbon-同一集群优先:
北京机房的内容中心优先调用 北京机房的用户中心, 若北京机房找不到任何存活的用户中心,才考虑调用南京机房的用户中心实例
@Slf4j
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
@Override
public Server choose(Object key) {
try {
// 拿到配置文件中的集群名称 BJ
String clusterName = nacosDiscoveryProperties.getClusterName();
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// 想要请求的微服务的名称
String name = loadBalancer.getName();
// 拿到服务发现的相关API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// 1. 找到指定服务的所有实例 A
List<Instance> instances = namingService.selectInstances(name, true);
// 2. 过滤出相同集群下的所有实例 B
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
.collect(Collectors.toList());
// 3. 如果B是空,就用A
List<Instance> instancesToBeChosen = new ArrayList<>();
if (CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToBeChosen = instances;
log.warn("发生跨集群的调用, name = {}, clusterName = {}, instances = {}",
name,
clusterName,
instances
);
} else {
instancesToBeChosen = sameClusterInstances;
}
// 4. 基于权重的负载均衡算法,返回1个实例
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
log.info("选择的实例是 port = {}, instance = {}", instance.getPort(), instance);
return new NacosServer(instance);
} catch (NacosException e) {
log.error("发生异常了", e);
return null;
}
}
}
/**
* 这个是为了调用 Nacos 内置的权重的负载均衡算法 ,
* protected static Instance getHostByRandomWeight(List<Instance> hosts)
* 不能直接调用 getHostByRandomWeight,所以继承 该方法的类,然后调用
*/
class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeight2(List<Instance> hosts) {
return getHostByRandomWeight(hosts);
}
}