ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] ***** # 1. 传统的负载均衡方式 ``` 1-1. 服务端负载均衡 1-2. 客户端负载均衡 ``` **1-1 服务端负载均衡** ``` ngnix是部署在服务端的,故称为服务端负载均衡 ``` ![](https://img.kancloud.cn/e9/21/e9213e60fdea046cdaafafebd08368fe_548x321.png) **1-2 客户端负载均衡** ``` 在内容中心中获取用户中心的实例,在内容中心中定义负载均衡的规则,故称为客户端负载均衡 ``` ![](https://img.kancloud.cn/f8/79/f879e8b1a2bc9b01b435bdeb6b4f0926_701x439.png) # 2. 手写一个客户端负载均衡器 ``` 目标: 随机选择实例 思路: 1. 通过nacos获取服务端实例列表 2. 通过算法随机选择 3. 代码如下: //获取nacos上ali-pay-service所有的实例 List<ServiceInstance> instances = discooveryClient.getInstances("ali-pay-service"); //获取请求地址 List<String> targetURLS = instances.stream().map(instance -> instance.getUri().toString() + "/users/{id}").collect(Collectors.toList()); //写随机算法,获取随机下标 int i = ThreadLocalRandom.current().nextInt(targetURLS.size()); //使用restTemplate请求 restTemplate.getForObject(targetURLS.get(i), null); ``` # 3. Ribbon实现负载均衡 **3-1 什么是Ribbon?** ``` Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起. Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等. Ribbon负载均衡主要是轮询算法,分为以下几步: 1.根据服务别名,从eureka / nacos获取服务提供者的客户端列表 2.将列表缓存到本地,即消费者客户端的jvm中 3.获取提供者客户端下标(总请求数%服务提供者数), 得到调用的服务客户端的实际地址 ``` ![](https://img.kancloud.cn/42/76/4276d1e51432482a42245ea23f765c47_740x357.png) **3-2 Ribbon实现负载均衡** ``` 1. 添加Ribbon依赖 2. 在RestTemplate定义的bean上添加注解 @LoadBalanced 3. 添加配置(可以默认配置) 4. 修改代码 restTemplate.getForObject("http://ali-pay-service/users/{userId}", null); ``` **3-3 Ribbon的组成** ![](https://img.kancloud.cn/98/5e/985eb6ffd9d5ae60f88f91a23a891e5a_887x401.png) **3-4 Ribbon内置的负载均衡规则** ![](https://img.kancloud.cn/b0/a3/b0a34e51ee967d144552093188c1a80b_860x496.png) **3-5 细粒度配置自定义** **01-Java代码方式** ``` /** * 修改Ribbon负载均衡规则 * 注: 此类要建在启动类之外 * 父子上下文: * 1. 启动类上有@ComponentScan扫描注解,默认会扫描当前类及下属所有包中相关注解(父上下文) * 2. Ribbon规则配置有@Configuration注解为子上下文,若在父包下,会出现父子上下文重叠 * 会导致事务不生效,重点: 因此配置ribbon规则时,不要让RibbonConfiguration被@ComponentScan扫描到 * */ @Configuration public class RibbonConfiguration { @Bean public IRule ribbonRule() { return new RandomRule(); } } ``` ``` /** * java代码修改ribbon的负载均衡规则 */ @Configuration @RibbonClient(name = "ali-pay-service", configuration = RibbonConfiguration.class) public class UerCenterRibbonConfiguration { } ``` **02-配置属性方式** ``` ali-pay-service: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule ``` **03-俩种配置方式对比** ``` [配置属性的方式] 比 [代码配置方式] 的优先级更高!!! ``` ![](https://img.kancloud.cn/f3/ce/f3ce24e46ee544f7cf5c35be16174029_795x302.png) **04-ribbon负载均衡规则全局配置** ``` /** * java代码修改ribbon负载均衡规则全局配置 * RibbonConfiguration 类就可以放在启动类下被@ComponentScan扫描到 */ @Configuration @RibbonClients(defaultConfiguration = RibbonConfiguration.class) public class UerCenterRibbonConfiguration { } ``` **05-ribbon饥饿加载** ``` Ribbon默认是懒加载的, 就是当restTemplate.getForObject("http://ali-pay-service/users/{userId}", null); 这段代码被调用时,才会加载,因此会导致首次请求过慢的问题. ``` ``` 解决方案: 通过饥饿加载解决, 添加配置如下: #饥饿加载配置 ribbon: eager-load: enabled: true #开启饥饿加载 clients: ali-pay-service #为哪些名称的client开启,多个用逗号分隔 ``` **3-6 扩展Ribbon** ``` 3-6-1. 扩展Ribbon-支持Nacos权重 取值在0到1之间, 值越大代表这个实例被调用的几率越大 Ribbon默认负载均衡规则不支持Nacos权重,所以扩展实现负载均衡权重的规则 ``` ``` /** * 基于权重的负载均衡算法 */ public class NacosWeightedRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; //读取配置文件并初始化 @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } @Override public Server choose(Object key) { try { ILoadBalancer loadBalancer = this.getLoadBalancer(); BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer)loadBalancer; //想要请求的微服务的名称 String name = baseLoadBalancer.getName(); //实现负载均衡的算法, 借助nacos已有的算法, 拿到服务发现相关的API NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); //nacos client 自动通过基于权重的负载均衡算法,给我们选择一个实例 Instance instance = namingService.selectOneHealthyInstance(name); return new NacosServer(instance); } catch (NacosException e) { return null; } } } ``` ![](https://img.kancloud.cn/8c/ef/8cef9b6e9f37d70214aa7f3b2d84f8b6_826x459.png) ``` 3-6-2. 扩展Ribbon-同一集群优先调用(同机房优先调用) 基于0.9.0版本,官方可能在下个版本加入此功能 public class NacosSameClusterWeightRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } @Override public Server choose(Object key) { try { //拿到配置文件中的集群名称(DL) spring.cloud.nacos.discovery.cluster-name=DL String clusterName = nacosDiscoveryProperties.getClusterName(); ILoadBalancer loadBalancer = this.getLoadBalancer(); BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer)loadBalancer; //想要请求的微服务的名称 String name = baseLoadBalancer.getName(); //实现负载均衡的算法, 借助nacos已有的算法, 拿到服务发现相关的API NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); // 1. 找到指定服务的所有实例(true代表健康的实例) -- 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 = Lists.newArrayList(); if (CollectionUtils.isEmpty(sameClusterInstances)) { instancesToBeChosen = instances; //发生跨集群的调用 } else { instancesToBeChosen = sameClusterInstances; } // 4. 基于权重的负载均衡算法,返回1个实例 Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen); return new NacosServer(instance); } catch (NacosException e) { return null; } } } class ExtendBalancer extends Balancer { public static Instance getHostByRandomWeight2(List<Instance> hosts) { return getHostByRandomWeight(hosts); } } ``` ``` 3-6-3. 扩展Ribbon-基于元数据的版本控制 场景: 服务间调用可能存在版本控制,如 服务A 的 V1版本必须调用服务B 的 V1版本, 服务A的V2版本必须调用服务B的V2版本 实现微服务之间的版本控制 代码参考: https://gitee.com/itmuch/spring-cloud-study/tree/master/2019-Spring-Cloud-Alibaba/microservice-consumer-movie-ribbon-rule-with-nacos-2 ``` **元数据就是一堆的描述信息,以map存储。举个例子:** ``` spring: cloud: nacos: metadata: # 自己这个实例的版本 version: v1 # 允许调用的提供者版本 target-version: v1 ``` **需求分析:** ``` 我们需要实现的有两点: 1. 优先选择同集群下,符合metadata的实例 2. 如果同集群加没有符合metadata的实例,就选择所有集群下,符合metadata的实例 ``` ``` @Slf4j public class NacosFinalRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public Server choose(Object key) { // 负载均衡规则:优先选择同集群下,符合metadata的实例 // 如果没有,就选择所有集群下,符合metadata的实例 // 1. 查询所有实例 A // 2. 筛选元数据匹配的实例 B // 3. 筛选出同cluster下元数据匹配的实例 C // 4. 如果C为空,就用B // 5. 随机选择实例 try { String clusterName = this.nacosDiscoveryProperties.getClusterName(); String targetVersion = this.nacosDiscoveryProperties.getMetadata().get("target-version"); DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer(); String name = loadBalancer.getName(); NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance(); // 所有实例 List<Instance> instances = namingService.selectInstances(name, true); List<Instance> metadataMatchInstances = instances; // 如果配置了版本映射,那么只调用元数据匹配的实例 if (StringUtils.isNotBlank(targetVersion)) { metadataMatchInstances = instances.stream() .filter(instance -> Objects.equals(targetVersion, instance.getMetadata().get("version"))) .collect(Collectors.toList()); if (CollectionUtils.isEmpty(metadataMatchInstances)) { log.warn("未找到元数据匹配的目标实例!请检查配置。targetVersion = {}, instance = {}", targetVersion, instances); return null; } } List<Instance> clusterMetadataMatchInstances = metadataMatchInstances; // 如果配置了集群名称,需筛选同集群下元数据匹配的实例 if (StringUtils.isNotBlank(clusterName)) { clusterMetadataMatchInstances = metadataMatchInstances.stream() .filter(instance -> Objects.equals(clusterName, instance.getClusterName())) .collect(Collectors.toList()); if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) { clusterMetadataMatchInstances = metadataMatchInstances; log.warn("发生跨集群调用。clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, targetVersion, clusterMetadataMatchInstances); } } Instance instance = ExtendBalancer.getHostByRandomWeight2(clusterMetadataMatchInstances); return new NacosServer(instance); } catch (Exception e) { log.warn("发生异常", e); return null; } } @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } } /**负载均衡算法**/ public class ExtendBalancer extends Balancer { /** * 根据权重,随机选择实例 * * @param instances 实例列表 * @return 选择的实例 */ public static Instance getHostByRandomWeight2(List<Instance> instances) { return getHostByRandomWeight(instances); } } ``` ``` 3-6-4. 深入理解Nacos的Namespace Nacos通过Namespace做了环境隔离,只能调用相同Namespace下的实例,而不能跨Namespace调用. ```