1 负载均衡的两种方式
@Autowired
private DiscoveryClient discoveryClient;
//-------------------------------------------手写负载均衡----------------------------//
@GetMapping("/hello")
public String hello() {
// 1.拿到用户中心所有的实例信息
List<ServiceInstance> userInstances = discoveryClient.getInstances("user-center");
// 2.所有用户中心实例的请求地址
List<String> targetUrls = userInstances.stream()
.map((instance) -> instance.getUri().toString() + "/user/hello")
.collect(Collectors.toList());
//随机请求
int i = ThreadLocalRandom.current().nextInt(targetUrls.size());
log.info("请求的目标地址:{}", targetUrls.get(i));
return new RestTemplate().getForObject(targetUrls.get(i), String.class);
}
2 整合Ribbon
2.1 加依赖
spring-cloud-starter-alibaba-nacos-discovery 已经依赖ribbon了所以无需单独引入
使用ribbon无需写注解,无需写配置(需要修改另说)
2.2 hello world
@Configuration
public class RestTemplageConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@Autowired
private RestTemplate restTemplate;
@GetMapping("/hello")
public String hello() {
return restTemplate.getForObject("http://user-center/user/hello", String.class);
}
3 Ribbon组成
4 Ribbon内置的负载均衡规则
5 Ribbon细粒度配置
5.1 Java代码配置
Spring中的父子容器:https://yuanyu.blog.csdn.net/article/details/105294435
16.2 Customizing the Ribbon Client
The
CustomConfiguration
clas must be a@Configuration
class, but take care that it is not in a@ComponentScan
for the main application context. Otherwise, it is shared by all the@RibbonClients
. If you use@ComponentScan
(or@SpringBootApplication
), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the@ComponentScan
).
注意: UserCenterRibbonConfiguration需要被扫描到,RibbonConfiguration不能被扫描到,否则会成为全局配置。@SpringBootApplication会扫描当前包和子包
@Configuration
@RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {}
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}
5.2 用配置属性配置
<clientName>.ribbon.NFLoadBalancerRuleClassName
- com.netflix.loadbalancer.RandomRule
- com.nobug.client.config.NacosSameClusterWeightedRule
user-center:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
5.3 最佳实践总结
配置方式 | 优点 | 缺点 |
---|---|---|
代码配置 | 基于代码,更加灵活 | 有小坑(父子上下文) 线上修改得重新打包、发布 |
属性配置【优先级更高】 | 易上手 配置更加直观 线上修改无需重新打包、发布 优先级更高 | 极端场景下没有代码配置方式灵活 |
- 尽量使用属性配置,属性方式实现不了的情况下再考虑用代码配置
- 在同一个微服务内尽量保持单一性,比如统一使用属性配置,不要两种方式混用,增加定位代码的复杂性
6 Ribbon全局配置
方式一 : 让ComponentScan上下文重叠(强烈不建议使用)- 方式二【唯一正确的途径】 : @RibbonClients(defaultConfiguration=xxx.class)
RibbonConfiguration需要被@SpringBootApplication扫描
@Configuration
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)
public class RibbonConfiguration {
}
7 Ribbon支持的配置项
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
@Bean
public IPing ping(){
return new PingUrl();
}
//...
}
16.4 Customizing the Ribbon Client by Setting Properties
<clientName>.ribbon.NFLoadBalancerClassName
: Should implementILoadBalancer
<clientName>.ribbon.NFLoadBalancerRuleClassName
: Should implementIRule
<clientName>.ribbon.NFLoadBalancerPingClassName
: Should implementIPing
<clientName>.ribbon.NIWSServerListClassName
: Should implementServerList
<clientName>.ribbon.NIWSServerListFilterClassName
: Should implementServerListFilter
xxx-center:
ribbon:
NFLoadBalancerClassName: ILoadBalancer实现类
NFLoadBalancerRuleClassName: IRule实现类
NFLoadBalancerPingClassName: IPing实现类
NIWSServerListClassName: ServerList实现类
NIWSServerListFilterClassName: ServerListFilter实现类
8 Ribbon的饥饿加载
开启饥饿加载
ribbon:
eager-load:
enabled: true
clients: user-service ,xxx # 细粒度指定哪些具体的服务【多个使用逗号分隔,不在这个名单上的依然使用懒加载】
9 nacos && Ribbon
9.1 扩展Ribbon支持Nacos权重
http://www.imooc.com/article/288660
/**
* 扩展Ribbon支持Nacos权重
*/
//IRule
@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();
log.info("lb={}",loadBalancer);
//想要请求的微服务的名称
String name = loadBalancer.getName();
//拿到服务发现的相关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) {
//e.printStackTrace();
log.error(e.getErrMsg());
}
return null;
}
}
9.2 扩展Ribbon同一集群优先调用
/**
* 扩展Ribbon-同一集群优先调用
*/
@Slf4j
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {}
@Override
public Server choose(Object o) {
//1.找到指定服务的所有实例 A
//2.过滤出相同集群下的所有实例 B
//3.如果B是空,就用A
//4.基于权重的负载均衡算法,返回1个实例
try {
//获取到配置中的集群名称 SH
//spring.cloud.nacos.discovery.cluster-name=SH
String clusterName = nacosDiscoveryProperties.getClusterName();
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//想要请求的微服务的名称
String name = loadBalancer.getName();
//拿到服务发现的相关API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
//1.找到指定服务的所有实例
List<Instance> instances = namingService.selectInstances(name, true);
//2.过滤出相同集群下的所有实例
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
.collect(Collectors.toList());
// 3.如果上述2为空,就用1
List<Instance> instancesToBeChosen;
if (CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToBeChosen = instances;
log.warn("发生了跨集群的调用,name = {}, clusterName = {}, instances = {}", name, clusterName, instances);
} else {
instancesToBeChosen = sameClusterInstances;
log.info("同一集群调用,name = {}, clusterName = {}, instances = {}", name, clusterName, instances);
}
//4.基于权重的负载均衡算法,返回一个实例
Instance instance = ExtendsBalancer.getHostByRandomWeightExtends(instancesToBeChosen);
log.info("选择的实例是 instance = {}", instance);
return new NacosServer(instance);
} catch (Exception e) {
//e.printStackTrace();
log.error(e.getMessage());
return null;
}
}
}
/**
*
*/
class ExtendsBalancer extends Balancer {
public static Instance getHostByRandomWeightExtends(List<Instance> hosts) {
return getHostByRandomWeight(hosts);
}
}
扩展Ribbon支持基于元数据的版本管理:http://www.imooc.com/article/288674