Java 客戶端負載均衡


  • 客戶端側負載均衡

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

圖片名稱

自定義實現Client Random負載均衡

  1. 獲取所有的服務list
  2. 隨機獲取需要訪問的服務信息
				// 自定義客戶端負載均衡能力
        // 獲取所有用戶中心服務的實例列表
        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 源碼

組成接口

![image-20190713132523310](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713132523310.png)

內置負載均衡規則

![image-20190713133911384](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713133911384.png)

配置方式

  • 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 # 規則類的全路徑名稱
圖片名稱
  • 對比

![image-20190713142916635](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713142916635.png)

  • 最佳使用
    • 盡量使用屬性配置
    • 在同一個微服務中盡量保持單一配置,不要混合使用,增加定位復雜性

Tip

在使用Ribbon的時候,配置class一定不能處於啟動類的同級目錄及其子目錄,否則會導致父子上下文重疊問題,帶來的結果就是,Ribbon規則會被配置稱全局配置規則,從而被所有微服務應用。

![image-20190713141743025](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713141743025.png)

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 替代自定義實現

  1. 添加依賴(Spring-Cloud-Alibaba-Nacos-Discovery已經依賴了Ribbon,因此不需要額外依賴)

  2. 添加注解(只需要在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();
        }
    
  3. 添加配置,直接使用

            ResponseEntity<UserDTO> userEntity = restTemplate.getForEntity(
                    "http://user-center/users/{userId}",
                    UserDTO.class, userId
            );
    

擴展Ribbon - 支持Nacos權重

  • 實現接口com.netflix.loadbalancer.IRule
  • 實現抽象類 com.netflix.loadbalancer.AbstractLoadBalancerRule

![image-20190713150041397](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713150041397.png)

@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);
    }
}

擴展Ribbon - 基於元數據的版本控制


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM