客户端负载均衡

手写负载均衡

    private final DiscoveryClient discoveryClient;

    @GetMapping("/{id}")
    public ShareDto findById(@PathVariable Integer id){
        Share share = shareService.findById(id);
        List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
//        String url = instances.stream()
//                .map(instance->instance.getUri()+"/user/{id}")
//                .findFirst()
//                .orElseThrow(()->new IllegalArgumentException("当前没有实例!"));

        List<String> targetUrls = instances.stream()
                .map(instance -> instance.getUri() + "/user/{id}")
                .collect(toList());
        //随机获取指定一个,达到负载均衡的目的
        int i = ThreadLocalRandom.current().nextInt(targetUrls.size());
        String url = targetUrls.get(i);

        UserDto userDto = restTemplate.getForObject(
                url,
                UserDto.class,
                share.getId());
        log.info("请求的目标地址{}",url);
        ShareDto shareDto = new ShareDto();
        BeanUtils.copyProperties(share,shareDto);
        shareDto.setWxNickname(userDto.getWxNickname());
        return shareDto;
    }

使用Allow parallel run 启动多个应用,注意改server port

使用Ribbon实现负载均衡

RestTemplate 添加负载均衡注解

@MapperScan("com.fly")
@SpringBootApplication
public class ContentApplication {

    public static void main(String[] args) {
        SpringApplication.run(ContentApplication.class, args);
    }

    @LoadBalanced  //负载均衡
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

使用,不需要discoveryClient了

 /**
     * 使用Ribbon 实现负载均衡
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public ShareDto findById(@PathVariable Integer id){
        Share share = shareService.findById(id);
        UserDto userDto = restTemplate.getForObject(
                "http://user-center/user/{userId}",
                UserDto.class,
                share.getId());
        ShareDto shareDto = new ShareDto();
        BeanUtils.copyProperties(share,shareDto);
        shareDto.setWxNickname(userDto.getWxNickname());
        return shareDto;
    }

Ribbon组成

Ribbon内置的负载均衡规则

默认为轮询RoundRobinRule

自定义颗粒度配置java

com.fly.contentcenter.configuration.UserCenterConfiguration

@Configuration
@RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
public class UserCenterConfiguration {
}

com.fly.ribbonconfigration.RibbonConfiguration

@Configuration
public class RibbonConfiguration {
    @Bean
    public IRule randomRule(){
        //随机规则
        return new RandomRule();
    }
}

RibbonConfiguration 要放到SpringBootApplication启动类之外,防止被扫描到,出现父子上下文重叠问题,如果父子上下文扫描重叠就会导致事务不生效。

一般我们在Spring的配置文件application.xml中对Service层代码配置事务管理,可以对Service的方法进行AOP增强或事务处理如事务回滚,但是遇到一个问题,在Controller类中调用Service层方法,配置的事务管理会失效,查询相关资料发现原因。其实Spring和SpringMVC俩个容器为父子关系,Spring为父容器,而SpringMVC为子容器。也就是说application.xml中应该负责扫描除@Controller的注解如@Service,而SpringMVC的配置文件应该只负责扫描@Controller,否则会产生重复扫描导致Spring容器中配置的事务失效。

在Spring的applicationContext.xml文件中配置配上下面这段代码:

<context:component-scan base-package="com.chuxin.platform,com.chuxin.core">
      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

而在SpringMVC的dispatcher-servlet.xml配置文件中配置下面这段代码:


<context:component-scan base-package="com.chuxin.platform.controller" use-default-filters="false">
      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

自定义颗粒度配置 配置属性方式,推荐使用这种

# <clientName>
user-center:  
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

两种方式对比

全局配置

方式一:将UserCenterConfiguration放到springboot可以扫描的地方,不推荐
方式二:

@Configuration
//@RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
@RibbonClients(defaultConfiguration = RibbonConfiguration.class) //全局配置
public class UserCenterConfiguration {
}

其它配置

### 开启饥饿加载,因为懒加载第一次请求很慢 ``` # 默认是懒加载,开启饥饿加载 ribbon: eager-load: enabled: true clients: user-center # 多个用,分割 ``` ### 扩展Ribbon-支持Nacos权重 ``` /** * @see com.alibaba.nacos.api.naming.NamingService#selectOneHealthyInstance * * 扩展Ribbon支持Nacos权重 */ @Slf4j public class NacosWeightRandomRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) {
}

@Override
public Server choose(Object o) {
    DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) this.getLoadBalancer();
    String name = loadBalancer.getName();//想要请求的微服务的名称
    try {
        Instance instance = discoveryProperties.namingServiceInstance().selectOneHealthyInstance(name);
        log.info("选中的instance = {}", instance);
        return new NacosServer(instance);
    }catch (NacosException e) {
        log.error("发生异常", e);
        return null;
    }
}

}

@Configuration
public class RibbonConfiguration {
@Bean
public IRule randomRule(){
//随机规则
// return new RandomRule();
return new NacosWeightRandomRule();
}
}

### 扩展Ribbon-同一集群优先调用

/**

  • 扩展Ribbon-同一集群优先调用
    */
    @Slf4j
    public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
    }

    @Override
    public Server choose(Object o) {
    try {
    // 配置文件的集群名称
    String clusterName = nacosDiscoveryProperties.getClusterName();
    BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
    // 想要请求的微服务的名称
    String name = loadBalancer.getName();

         NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
         // 找到指定服务的所有实例
         List<Instance> instances = namingService.selectInstances(name, true);
         //过滤出相同集群下的所有实例
         List<Instance> sameClusterInstances  = instances.stream()
                 .filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
                 .collect(Collectors.toList());
         List<Instance> instancesToBeChosen = new ArrayList<>();
         if (CollectionUtils.isEmpty(sameClusterInstances)){
             instancesToBeChosen = instances;
             log.warn("发生跨集群的调用, name = {}, clusterName = {}, instances = {}",
                     name,
                     clusterName,
                     instances
             );
         }else {
             instancesToBeChosen = sameClusterInstances;
         }
         // 4. 基于权重的负载均衡算法,返回1个实例
         Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
         log.info("选择的实例是 port = {}, instance = {}", instance.getPort(), instance);
    
     } catch (NacosException e) {
         e.printStackTrace();
     }
     return null;
    

    }
    }
    class ExtendBalancer extends Balancer {
    public static Instance getHostByRandomWeight2(List hosts) {
    return getHostByRandomWeight(hosts);
    }
    }


### 扩展Ribbon支持基于元数据的版本管理

/**

  • 扩展Ribbon支持基于元数据的版本管理
  • spring:
  • cloud:
  • nacos:
    
  •     metadata:
    
  •       # 自己这个实例的版本
    
  •       version: v1
    
  •       # 允许调用的提供者版本
    
  •       target-version: v1
    

*/

@Slf4j
public class NacosFinalRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;

@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}

@Override
public Server choose(Object o) {
    try {
        // 配置文件的集群名称
        String clusterName = nacosDiscoveryProperties.getClusterName();
        String targetVersion = nacosDiscoveryProperties.getMetadata().get("target-version");
        BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
        // 想要请求的微服务的名称
        String name = loadBalancer.getName();

        NamingService namingService = 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);
            }
        }

        // 基于权重的负载均衡算法,返回1个实例
        Instance instance = ExtendBalancer.getHostByRandomWeight2(clusterMetadataMatchInstances);
        log.info("选择的实例是 port = {}, instance = {}", instance.getPort(), instance);

    } catch (NacosException e) {
        e.printStackTrace();
    }
    return null;
}
static class ExtendBalancer extends Balancer {
    public static Instance getHostByRandomWeight2(List<Instance> hosts) {
        return getHostByRandomWeight(hosts);
    }
}

}

### 不能跨namespace调用

猜你喜欢

转载自www.cnblogs.com/fly-book/p/12818925.html
今日推荐