- 客户端侧负载均衡
在下图中,负载均衡能力算法是由内容中心提供,内容中心相对于用户中心来说,是用户中心的客户端,所以又被称为客户端侧负载均衡
自定义实现Client Random负载均衡
- 获取所有的服务list
- 随机获取需要访问的服务信息
// 自定义客户端负载均衡能力 // 获取所有用户中心服务的实例列表 ListtargetUris = instances.stream().map(i -> i.getUri().toString() + "/users/{id}").collect(Collectors.toList()); //获取随机实例 int i = ThreadLocalRandom.current().nextInt(targetUris.size()); //调用用户微服务 /users/{userId} log.info("请求的目标地址:{}", targetUris.get(i)); ResponseEntity userEntity = restTemplate.getForEntity( targetUris.get(i), UserDTO.class, userId );
Ribbon
什么是Ribbon
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法。
Github:
组成接口
内置负载均衡规则
配置方式
- Java 代码配置
/** * RibbonConfiguration for TODO * * @author Isaac.Zhang | 若初 * @since 2019/7/13 */@Configurationpublic class RibbonConfiguration { @Bean public IRule ribbonRule(){ return new RandomRule(); }}/*------------------------------------------------------------*//** * UserCenterRibbonConfiguration for 自定义实现User-center service ribbon client * * @author Isaac.Zhang | 若初 * @since 2019/7/13 */@Configuration@RibbonClient(name = "user-center", configuration = RibbonConfiguration.class)public class UserCenterRibbonConfiguration { }/*------------------------------------------------------------*/@Configuration//@RibbonClient(name = "user-center", configuration = RibbonConfiguration.class) //作用域为 user-center@RibbonClients(defaultConfiguration = RibbonConfiguration.class) //作用域为全局public class UserCenterRibbonConfiguration {}
- 使用配置文件
user-center: # service name ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 规则类的全路径名称
- 对比
- 最佳使用
- 尽量使用属性配置
- 在同一个微服务中尽量保持单一配置,不要混合使用,增加定位复杂性
==Tip==
在使用Ribbon的时候,配置class一定不能处于启动类的同级目录及其子目录,否则会导致父子上下文重叠问题,带来的结果就是,Ribbon规则会被配置称全局配置规则,从而被所有微服务应用。
The
CustomConfiguration
class 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
).
Ribbon 饥饿加载
默认情况下,Ribbon是懒加载
,在第一次请求的时候才会创建客户端。
ribbon: eager-load: enabled: true # 饥饿加载激活 clients: user-center,xxx,xxx # 为哪些clients开启
使用Ribbon 替代自定义实现
添加依赖(Spring-Cloud-Alibaba-Nacos-Discovery已经依赖了Ribbon,因此不需要额外依赖)
添加注解(只需要在RestTemplate IOC添加
@LoadBalance
)/** * 在Spring 容器中,创建一个对象,类型是{@link RestTemplate} * 名称/ID 为 restTemplate *
* {@link LoadBalanced} 为RestTemplate整合Ribbon调用 * * @return restTemplate */ @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } 添加配置,直接使用ResponseEntity
userEntity = restTemplate.getForEntity( "http://user-center/users/{userId}", UserDTO.class, userId );
扩展Ribbon - 支持Nacos权重
- 实现接口
com.netflix.loadbalancer.IRule
- 实现抽象类
com.netflix.loadbalancer.AbstractLoadBalancerRule
@Slf4j@RequiredArgsConstructor(onConstructor = @__(@Autowired))public class NacosWeightRule4Ribbon extends AbstractLoadBalancerRule { private final NacosDiscoveryProperties nacosDiscoveryProperties; @Override public void initWithNiwsConfig(IClientConfig clientConfig) { // 读取配置文件,并初始化 NacosWeightRule4Ribbon } @Override public Server choose(Object key) { try { // ILoadBalancer 是Ribbon的入口,基本上我们想要的元素都可以在这个对象中找到 BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); log.info("NacosWeightRule4Ribbon lb = {}", loadBalancer); // 想要请求的微服务名称 String name = loadBalancer.getName(); // 实现负载均衡算法 // 可得到服务发现相关的API(nacos内部实现) NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); // nacos client 通过基于权重的负载均衡算法,选择一个实例 Instance instance = namingService.selectOneHealthyInstance(name); log.info("port = {}, weight = {}, instance = {}", instance.getPort(), instance.getWeight(), instance); return new NacosServer(instance); } catch (NacosException e) { log.error("NacosWeightRule4Ribbon {}", e.getMessage()); } return null; }}@Configuration@RibbonClients(defaultConfiguration = NacosWeightRule4Ribbon.class) //全局配置public class UserCenterRibbonConfiguration {}
拓展Ribbon - 同集群优先
public Server choose(Object key) { try { // 获取到配置文件中的集群名称 BJ String clusterName = nacosDiscoveryProperties.getClusterName(); BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); String serviceName = loadBalancer.getName(); //获取服务发现的相关API NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); // 1. 找到指定服务的所有实例 A Listinstances = namingService.selectInstances(serviceName, true); // 2. 过滤出相同集群下的所有实例 B List sameClusterInstances = instances.stream() .filter(instance -> Objects.equals(instance.getClusterName(), clusterName)) .collect(Collectors.toList()); // 3. 如果B为空,则使用 A List instancesChoosen = new ArrayList<>(); if (CollectionUtils.isEmpty(sameClusterInstances)) { instancesChoosen = instances; log.warn("发生跨集群调用,name = {},clusterName = {}", serviceName, clusterName); } else { instancesChoosen = sameClusterInstances; } // 4. 基于权重的负载均衡算法,返回一个实例 A Instance instance = ExtendBalancer.getHostByRandomWeightOverride(instancesChoosen); log.info("choose instance is : port = {}, instance = {}", instance.getPort(), instance); return new NacosServer(instance); } catch (NacosException e) { e.printStackTrace(); log.error(e.getErrMsg()); } return null; }/*** 调用Nacos内部方法,进行一次包装*/class ExtendBalancer extends Balancer { public static Instance getHostByRandomWeightOverride(List hosts) { return getHostByRandomWeight(hosts); }}