- 客戶端側負載均衡
在下圖中,負載均衡能力算法是由內容中心提供,內容中心相對於用戶中心來說,是用戶中心的客戶端,所以又被稱為客戶端側負載均衡

自定義實現Client Random負載均衡
- 獲取所有的服務list
- 隨機獲取需要訪問的服務信息
// 自定義客戶端負載均衡能力
// 獲取所有用戶中心服務的實例列表
List<String> targetUris = 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<UserDTO> userEntity = restTemplate.getForEntity(
targetUris.get(i),
UserDTO.class, userId
);
Ribbon
什么是Ribbon
Ribbon是Netflix發布的開源項目,主要功能是提供客戶端的軟件負載均衡算法。
Github: Ribbon 源碼
組成接口

內置負載均衡規則

配置方式
- Java 代碼配置
/**
* RibbonConfiguration for TODO
*
* @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
* @since 2019/7/13
*/
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
}
/*------------------------------------------------------------*/
/**
* UserCenterRibbonConfiguration for 自定義實現User-center service ribbon client
*
* @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
* @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 * <bean id ="restTemplate" class="XXX.RestTemplate" /> * {@link LoadBalanced} 為RestTemplate整合Ribbon調用 * * @return restTemplate */ @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
-
添加配置,直接使用ResponseEntity<UserDTO> 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
List<Instance> instances = namingService.selectInstances(serviceName, true);
// 2. 過濾出相同集群下的所有實例 B
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
.collect(Collectors.toList());
// 3. 如果B為空,則使用 A
List<Instance> 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<Instance> hosts) {
return getHostByRandomWeight(hosts);
}
}