服务注册与负载均衡

  • SpringCloud Alibaba
  • 服务发现
  • Nacos
  • DiscoverClient
  • 服务发现的领域模型
  • 元数据
  • 负载均衡
  • 使用Ribbon实现负载均衡
  • Ribbon组成
  • Ribbon自定义配置一(实现细粒度配置Ribbon负载均衡)
  • Java代码实现
  • 配置文件实现
  • Ribbon自定义配置二(实现全局配置Ribbon负载均衡)
  • Ribbon支持的配置项
  • Java代码实现
  • 配置文件实现
  • Ribbon的懒汉与饿汉模式
  • Ribbon配置权重
  • Ribbon实现同集群优先级调用
  • Ribbon支持基于元数据的版本管理
  • Nacos中的namespace作用


SpringCloud Alibaba

服务发现者服务消费者是成对存在的


服务发现

服务中心会定时的向注册在自己的微服务发送心跳检测,防止该微服务突然崩溃导致的雪崩问题。


Nacos

区别于Eureka的另外一种服务发现组件,Nacos是阿里巴巴团队出品的一款开源服务发现组件,安装可以直接百度Nacos下载。
安装完成后可以在控制台中启动,启动完成之后访问8848端口。

<!--xxxx-spring-boot-starter--> 其他项目对spring的支持
    <!--spring-cloud-starter-{spring cloud子项目的名称}-[{模块名称}]--> springcloud自己模块下的
    <!--feign spring-cloud-starter-openfeign--> 
    <!--sentinel spring-cloud-starter-alibaba-sentinel-->
<!-- Nacos依赖-->
<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<dependencyManagement>
  <dependencies>
        <!--整合spring cloud-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
            <!--整合spring cloud alibaba-->
        <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-alibaba-dependencies</artifactId>
              <version>0.9.0.RELEASE</version>
              <type>pom</type>
              <scope>import</scope>
          </dependency>
      </dependencies>
</dependencyManagement>

不需要加注解了(之前Eureka需要加Client注解),在配置文件中写如下配置。

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

这里要注意,微服务是注册到了我们从控制台启动的Nacos,所以不需要在我们的微服务中启动一个Eureka一样的服务。


DiscoverClient

使用该对象可以查询到所有当前注册在服务中心的所有服务,只需要直到该服务的名称,适用于Eureka/Zookeeper/consul等服务发现端。

new DiscoverClient.getInstances("");//这里填写ServiceId,也就是应用名称,注册到服务端的那个

使用DiscoverClient可以获取对应服务的URL地址,所以可以配合RestTemplate来调用其他微服务。

String target = discoverClient.getInstances("xxxx")
				.stream().map( c -> c.getUri().toString() + "/users/{id}" )
				.findFirst()
				.orElseThrow( () -> new IllegalArgumentException("xxxx异常") );

经典的Lambda表达式,超级炫酷。


服务发现的领域模型

namespace : 用以区别环境,是一种隔离的状态,如生产环境,开发环境,测试环境等。
cluster :集群,一个集群下有多个微服务运行。

ps : 需要在yml中配置,在nacos节点下配置。


元数据

nacos概念,可以提供描述信息,可以让微服务调用更加灵活。

Nacos数据(如配置和服务)描述信息,如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标签 (label),从作用范围来看,分为服务级别的元信息、集群的元信息及实例的元信息。

两种配置方式

  • 控制台指定 (在Nacos控制台中指定)
  • 配置文件指定
metadata:
  x: y

负载均衡

  • 服务器端负载均衡 Nginx ==> Tomcat
  • 客户端负载均衡 类似于Eureka和Nacos的负载均衡

nginx是一个HTTP Server ,侧重关心HTTP协议层面的传输和访问控制
Tomcat是一个应用服务器,比如:将应用部署到tomcat服务器

手动(低配版)实现负载均衡

使用随机数在获取的微服务列表中随机选择URL来实现负载均衡、


使用Ribbon实现负载均衡

Ribbon是Netflix开源的客户端负载均衡器,为我们提供了负载均衡的算法。
spring-cloud-starter-alibaba-nacos-discovery依赖中已经包含了Ribbon的依赖。
添加注解@LoadBances

@Bean
@LoadBances//使用Ribbon扩展RestTemplate
public RestTemplate getRestTemplate(){
	return new RestTemplate();
}

再次使用restTemplate的getForObject(),在其中将URL的地址换成对应微服务的serviceId即可调用相关的微服务(会自动实现负载均衡)。
相关URL为:http://微服务名称/接口名称

Ribbon组成

spring cloud alibaba 新手教程 spring cloud alibaba从入门到进阶_微服务


Ribbon在没有Zone的环境下是默认使用轮询的算法实现负载均衡的。


Ribbon自定义配置一(实现细粒度配置Ribbon负载均衡)

Java代码实现

父子上下文不可以重叠,重叠后子上下文就会覆盖掉父上下文,导致所有的使用到Ribbon的地方都会采用自己所定义的Ribbon规则,无法实现微服务A使用单独的Ribbon规则。

@Configuration //这个包要配置在SpringBoot主类之外
public class ribbonConfiguration(){
	@Bean
	public IRule getRule(){
		//设置随机的ribbon规则
		return new RandomRule();
	}
}
@Configuration //配置在SpringBoot主类之下。并且设置对应的serviceId
@RibbonClient(name = "user-center" , confiuration=ribbonConfiguration.class)
public class UserCenterRibbonConfiguration{
	
}

配置文件实现

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

spring cloud alibaba 新手教程 spring cloud alibaba从入门到进阶_spring_02

Ribbon自定义配置二(实现全局配置Ribbon负载均衡)

修改注解@RibbonClient(name = "user-center" , confiuration=ribbonConfiguration.class)@RibbonClients( defaultConfiuration=ribbonConfiguration.class)

commond + option + b 查看当前IDEA接口所有的实现类

Ribbon支持的配置项

上图中所有的配置项都可以去自定义,都可以通过Java以及配置文件的方式去实现。

Java代码实现

按照代码实现,就按照IRule去new一个新的实现类即可。

配置文件实现

spring cloud alibaba 新手教程 spring cloud alibaba从入门到进阶_负载均衡_03

Ribbon的懒汉与饿汉模式

Ribbon在默认时是按照懒汉模式加载的,也就是第一次访问的时候才进行配置与初始化,只要在配置文件中开启饥饿模式就可以实现饿汉模式。

ribbon:
  eager-load:
    enabled: true

Ribbon配置权重

Ribbon可以为实例配置权重,从而可以根据不同实例所在环境不同分配不同的优先级。

spring cloud alibaba 新手教程 spring cloud alibaba从入门到进阶_微服务_04


如何自定义实现Ribbon配置权重 ====> Ribbon结合Nacos配置权重

Ribbon实现同集群优先级调用

如上问一样,使用自定义的Ribbon实现对应算法,继承AbstractLoadBalanceRule,实现对应的方法。
使用NacosDiscoveryProperties可以获取配置文件中规定的属性,例如规定了什么集群。

  1. 找到指定服务的所有实例 A
  2. 过滤出相同集群下的所有实例 B
  3. 如果 B 是空 ,就用 A
  4. 基于权重的负载均衡算法,返回一个实例
package com.itmuch.contentcenter.configuration;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.core.Balancer;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

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

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

    @Override
    public Server choose(Object key) {
        try {
            // 拿到配置文件中的集群名称 BJ
            String clusterName = nacosDiscoveryProperties.getClusterName();

            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            // 想要请求的微服务的名称
            String name = loadBalancer.getName();

            // 拿到服务发现的相关API
            NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();

            // 1. 找到指定服务的所有实例 A
            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;
                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);

            return new NacosServer(instance);
        } catch (NacosException e) {
            log.error("发生异常了", e);
            return null;
        }
    }
}

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

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

如何自定义实现Ribbon配置权重 ====> Ribbon支持基于元数据的版本管理

Nacos中的namespace作用

namespace实现了一个隔离的效果,相当于在一个容器上实现了几种不同的服务注册,不同namespace上的项目都发现不了其他的namespace。