一、spring cloud alibaba学习(nacos,ribbon)

北城余情 提交于 2020-02-08 02:11:41

对比

Nacos

服务发现组建,也是配置服务器

用来解决服务a如何找到服务b、并且管理微服务

下载应用程序,并打开,以启动Nacos Server

加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>${latest.version}</version>
</dependency>

没有注解。

写配置,制定Nacos Server的地址、服务名称

spring:
  cloud:
    nacos:
      discovery:
        #指定nacos server的地址
        server-addr: localhost:8848
  application:
    #服务名称尽量用-,不要用_及特殊字符
    name: user-center

领域模型

namespace:主要用于隔离,例如隔离开发环境,生产环境,测试环境等。不能跨namespace调用实例。

group:把不同的微服务放到一个分组里,方便管理。(alibaba0.9.0中没有用到)

service:微服务

cluster:集群,对指定微服务的虚拟划分

instance:微服务实例

 

namespace和cluster:

namespace需要在控制台上先创建好

spring:
  cloud:
    nacos:
      discovery:
        #写控制台创建好的namespace的UUID,不能写名称!!
        namespace: XXX
        #集群名称自己起
        cluster-name: xxx

元数据

Nacos的数据描述信息,如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标签。

从作用范围看,分为服务级别元数据、集群级别元数据、实例级别元数据。

目前spring cloud alibaba0.9.0中,服务和集群级别元数据暂时用不上。

作用:

提供描述信息。

让微服务调用更加灵活。(微服务的版本控制,例如v1client只能调用v1server之类)

如何设置?1.控制台设置2.配置文件

spring:
  cloud:
    nacos:
      discovery:
        metadata:
          #key: value的形式即可
          a: b
          haha: hehe
          version: v1

 

负载均衡

实现方式:

服务器端负载均衡:

客户端负载均衡:

Ribbon

是开源的客户端负载均衡器。

ribbon会从nacos上获取要调用的服务地址列表,通过自有算法算出一个实例,交给restTemplate去请求。

不需要加依赖,因为nacos-discovery包中已经包含ribbon

写注解

//注解需要写在restTemplate上,为restTemplate整合ribbon
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
    return new RestTemplate();
}

没有配置

当restTemplate请求时,ribbon会自动把server在nacos中的url的http地址转化成server自己的服务名称,例如

http://localhost:8080/users/{id}会变为http://user-center/users/{id}

ribbon的组成

ribbon的负载均衡规则

默认是ZoneAvoidanceRule

细粒度配置ribbon规则

java代码的方式:

建两个类,一个用于绑定用户服务与ribbon规则类,另一个用于写ribbon规则的实现类

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

注意:RibbonConfiguration类所在的包要和启动类所在包平行,不能在一起!!否则会引发所有ribbon规则都会变成修改的,父子上下文相关问题。

因为@Configuration里面包含@Component,而启动类上的@SpringBootApplication中包含@ComponentScan,这个注解会扫描启动类所在的包及包下面的所有@Component、@Controller、@Repository、@Service等等,包括@Configuration,所以ribbon的配置类一定不能被启动类扫描到,否则会产生父子上下文扫描重叠的问题,导致事务不生效。在ribbon中会导致所有规则都变为改规则。

@Configuration
public class RibbonConfiguration{
    @Bean
    public IRule ribbonRule(){
        return new RandomRule();
    }
}

配置属性的方式:

user-center:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

所以对于代码配置基于代码,更加灵活,但更麻烦,有父子上下文的问题,线上修改还得重新打包发布。

属性配置的方式更简单也更直观,优先级更高,但极端场景下没有代码配置灵活。

所以微服务在能满足要求的前提下,尽量使用属性配置。在同一微服务中尽量保持单一性,不要一会属性一会代码。增加定位问题的复杂度。

ribbon的全局配置

方式一:让@ComeponentScan上下文重叠。(如上文所说,但完全不建议)

方式二:在RibbonClient注解类中,把@RibbonClient后面加s,configuration改为defaultConfiguration,且删掉name即可。

@Configuration
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration{
}

ribbon支持的配置项

上面表格中ribbon组成的各项均可,用java代码方式,仿照rule规则,在实现类中写实现方法即可。

配置属性的话:

ribbon的饥饿加载

ribbon默认情况下是懒加载的,所以导致首次请求都过慢,可通过饥饿加载解决。

写配置:

ribbon:
  eager-load:
    #开启饥饿加载
    enabled: true
    #为哪些服务开启饥饿加载,也是细粒度配置,用逗号隔开
    clients: user-center

ribbon配置基于nacos权重的负载均衡规则

权重取值在0-1之间,值越大代表这个实例被调用的几率越大。但在ribbon的规则中,并没有nacos权重相关,所以需自己写规则。

public class NacosWeightedRule extends AbstractLoadBalancerRule {
    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        //读取配置文件,并初始化NacosWeightedRule
    }

    @Override
    public Server choose(Object key) {
        try {
            //loadBalancer是ribbon的入口,想要的基本都有
            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            //想要请求的微服务的名称
            String name = loadBalancer.getName();

            //实现负载均衡算法
            //可拿到服务发现相关api
            NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
            //nacos client自动通过基于权重的负载均衡算法给我们选择一个实例。
            Instance instance = namingService.selectOneHealthyInstance(name);
            return new NacosServer(instance);
        } catch (NacosException e) {
            return null;
        }
    }
}

配置参照上面即可。

修改权重大小在nacos控制台,针对实例设置权重的数字即可。

ribbon同一集群优先调用的负载均衡规则

server和client端配置

spring:
  cloud:
    discovery:
      #设置集群名称,例如:HAOZI
      cluster-name: HAOZI

写规则

public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }

    @Override
    public Server choose(Object key) {
        try {
            //拿到配置文件中的集群名称 HAOZI
            String clusterName = nacosDiscoveryProperties.getClusterName();
            //loadBalancer是ribbon的入口,想要的基本都有
            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            //想要请求的微服务的名称
            String name = loadBalancer.getName();
            //可拿到服务发现相关api
            NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
            //1.找到指定服务的所有实例 A
            //true代表只拿健康的实例
            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 = new ArrayList<>();
            if(CollectionUtils.isEmpty(sameClusterInstances)){
                instancesToBeChosen = instances;
            }else{
                instancesToBeChosen = sameClusterInstances;
            }
            //4.基于权重的负载均衡算法,返回一个实例
            Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
            return new NacosServer(instance);
        } catch (NacosException e) {
            log.error("异常",e);
            return null;
        }
    }
}
//由于没有基于权重的负载均衡方法调用,所以通过源码找到,但源码的getHostByRandomWeight是protected的,
//所以写类继承该类并通过子类调用该方法并返回
class ExtendBalancer extends Balancer {
    public static Instance getHostByRandomWeight2(List<Instance> hosts){
        return getHostByRandomWeight(hosts);
    }
}

ribbon基于元数据的版本控制的负载均衡规则

写配置

spring:
  cloud:
    nacos:
        metadata: 
          # 自己这个实例的版本
          version: v1
          # 允许调用的提供者版本
          target-version: v1

写规则

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

    @Override
    public Server choose(Object key) {
        // 负载均衡规则:优先选择同集群下,符合metadata的实例
        // 如果没有,就选择所有集群下,符合metadata的实例
        try {
            //通过配置文件拿到集群名称
            String clusterName = this.nacosDiscoveryProperties.getClusterName();
            //通过元数据拿到可以调用的版本信息
            String targetVersion = this.nacosDiscoveryProperties.getMetadata().get("target-version");

            DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
            //微服务名称
            String name = loadBalancer.getName();
            //拿到服务相关api
            NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance();

            // 1. 查询所有实例 A
            List<Instance> instances = namingService.selectInstances(name, true);

            List<Instance> metadataMatchInstances = instances;
            // 2. 筛选元数据匹配的实例 B
            // 如果配置了版本映射,那么只调用元数据匹配的实例
            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;
            // 3. 筛选出同cluster下元数据匹配的实例 C
            // 如果配置了集群名称,需筛选同集群下元数据匹配的实例
            if (StringUtils.isNotBlank(clusterName)) {
                clusterMetadataMatchInstances = metadataMatchInstances.stream()
                        .filter(instance -> Objects.equals(clusterName, instance.getClusterName()))
                        .collect(Collectors.toList());
                // 4. 如果C为空,就用B
                if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) {
                    clusterMetadataMatchInstances = metadataMatchInstances;
                    log.warn("发生跨集群调用。clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, targetVersion, clusterMetadataMatchInstances);
                }
            }
            // 5. 随机选择实例
            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);
    }
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!