服务发现

为了实现多个微服务之间的调用,我们除了需要Feign这种调用组件外还得依赖服务发现组件。主要的原因是每个微服务所在的机器ip并非总是固定的,并且每个微服务都可能部署多个实例在不同的机器上,所以我们不能把依赖的微服务ip地址写在代码或配置文件里,我们需要有个组件去动态的管理,这就是为什么微服务架构里服务发现功能是必须的。

那么服务发现组件是怎么实现服务发现的呢?我们以大家比较熟悉的MySQL来做类比,通过MySQL简单说明一下服务发现机制的实现。如下图:
Spring Cloud Alibaba之服务发现组件 - Nacos

简单说明一下什么是服务提供者与服务消费者:

  • 服务提供者:服务的被调用方(即:为其他微服务提供接口的微服务)
  • 服务消费者:服务的调用方(即:调用其他微服务接口的微服务)
  • 例如:订单服务需要调用用户服务的接口,那么订单服务就是服务消费者,而用户服务则是服务提供者
  • 服务提供者与服务消费者实际描述的是微服务之间的调用关系,一般都是成对出现的

当微服务启动的时候会向服务发现组件注册自身信息,在上图中就类似于向MySQL发送一条insert语句,将服务的元数据如服务名称、ip地址及服务状态等信息插入到MySQL中,如上图的registry表数据所示,这个过程称之为服务注册,所以服务发现组件内部会都维护类似于这样的一张注册表。

微服务在注册完成后,会读取服务发现组件中保存的其他微服务的元数据并缓存一份到本地,就类似于向MySQL发送一条select all语句。这样在调用其他服务的时候,就不需要每次都去服务发现组件上查询,而是从本地缓存去查找调用地址,这样可以减轻服务发现组件的压力。所以上图中的调用箭头并没有指向服务发现组件,而是直接指向服务提供者。这样的好处是哪怕是服务发现组件挂掉了,还能从本地缓存中获取其他微服务的调用地址。到这一步微服务之间就可以互相发现了,即完成基本的服务发现

但微服务有可能会挂掉或下线,此时其他服务不应该去发现一个不存在的服务。所以每个服务启动且向服务发现组件注册完成之后,都会通过心跳机制告知存活状态。上图中用last_heartbeat字段表示,若某个服务在超过一定的时间都没有发送心跳包的话,就会被服务发现组件检测到,此时就会删除注册表里该服务的注册信息,并通知其他服务更新本地缓存(若有新注册的服务也会通知其他服务更新本地缓存)。


搭建Nacos Server

关于什么是Nacos,官方文档已经描述得很详细了,Nacos官方文档地址如下:

https://nacos.io/zh-cn/docs/what-is-nacos.html

所以这里只是简单概述一下,Nacos与Eureka一样,是服务发现组件,同时也是配置中心。Nacos解决了两个问题,一是服务A如何找到服务B;二是管理微服务的配置,让一个微服务的所有实例的配置都统一,并且可以实现配置修改后自动刷新等。

理论介绍也说得差不多了,本小节我们来动手搭建一个Nacos Server。过程很简单首先需要下载一个Nacos,下载地址如下:

https://github.com/alibaba/nacos/releases

然后我们需要选择一个合适的版本下载,即Nacos Server版本应尽量与Client端的版本对应。至于Client的版本我们可以到工程的pom.xml文件中找到Spring Cloud Alibaba的依赖管理项点击进去即可查看到:
Spring Cloud Alibaba之服务发现组件 - Nacos

如下可以看到Nacos Client的版本为1.0.0,所以与之对应选择1.0.0版本的Nacos Server进行下载:
Spring Cloud Alibaba之服务发现组件 - Nacos

注:我这里使用的Spring Cloud版本是Greenwich.SR1,Spring Cloud Alibaba的版本是0.9.0.RELEASE

由于不是生产环境所用,其实也无需严格选择对应的版本,只要能用就可以了,所以我这里选择下载最新的1.1.0版本(经过测试可用):
Spring Cloud Alibaba之服务发现组件 - Nacos

下载并解压后进入bin目录,双击startup.cmd,或在命令行中输入cmd startup.cmd即可运行Nacos Server:
Spring Cloud Alibaba之服务发现组件 - Nacos

启动成功:
Spring Cloud Alibaba之服务发现组件 - Nacos

使用浏览器访问localhost:8848进入Nacos Server的管理页面,此时需要输入账户密码,默认的账户密码都是nacos:
Spring Cloud Alibaba之服务发现组件 - Nacos

登录成功后,页面如下:
Spring Cloud Alibaba之服务发现组件 - Nacos

该管理页面支持中英文,可在右上角点击切换:
Spring Cloud Alibaba之服务发现组件 - Nacos

官方文档如下:

https://nacos.io/zh-cn/docs/quick-start.html


将微服务注册到Nacos

在上一小节中,我们已经完成了Nacos Server的搭建,而这一小节将演示如何将微服务注册到Nacos。我现在有一个用户中心微服务,其pom.xml文件如下,包含了Spring Cloud Alibaba及Nacos Client依赖,Spring Boot版本为2.1.6.RELEASE:

<dependencies>
    ...

    <!-- Nacos Client -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

<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>

在配置文件中配置一下nacos server的地址及端口:

spring:
  cloud:
    nacos:
      discovery:
        # 指定nacos server的地址
        server-addr: 127.0.0.1:8848
  application:
    # 服务名称,必须的配置项,否则不会向nacos注册
    name: user-center

  ...  

配置完成后启动项目,然后到nacos server的管理页面的服务列表上查看是否注册成功,注册成功的话会显示在服务列表里,如下:
Spring Cloud Alibaba之服务发现组件 - Nacos

点击详情可以看到详细信息:
Spring Cloud Alibaba之服务发现组件 - Nacos

经过以上这几个步骤,就可以非常简单地整合Nacos Client,并将微服务注册到Nacos Server上。通过同样的步骤,我将另一个内容中心微服务也注册到Nacos Server上。我们来写一个简单的测试用例,看看在内容中心上是否能发现用户中心,代码如下:

@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class DiscoveryClientTests {

    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 测试服务发现,证明内容中心能找到用户中心
     */
    @Test
    public void getServiceInstancesTest(){
        // 获取用户中心微服务的所有实例信息
        List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
        // 以json格式打印出来
        log.info(JsonUtil.obj2JsonPretty(instances));
    }
}

控制台输出的json信息如下,证明通过服务发现组件能让内容中心总是能找到用户中心:

[ {
  "serviceId" : "user-center",
  "host" : "192.168.190.1",
  "port" : 8080,
  "secure" : false,
  "metadata" : {
    "nacos.instanceId" : "192.168.190.1#8080#DEFAULT#DEFAULT_GROUP@@user-center",
    "nacos.weight" : "1.0",
    "nacos.cluster" : "DEFAULT",
    "nacos.healthy" : "true",
    "preserved.register.source" : "SPRING_CLOUD"
  },
  "uri" : "http://192.168.190.1:8080",
  "scheme" : null,
  "instanceId" : null
} ]

Nacos服务发现的领域模型

下图整理了Nacos服务发现的领域模型,我们将围绕该图进行介绍:
Spring Cloud Alibaba之服务发现组件 - Nacos

1、上图最外层的是Namespace,对应管理界面的命名空间,如下:
Spring Cloud Alibaba之服务发现组件 - Nacos

Namespace主要用作隔离,默认为public,例如我们有三个环境:开发、测试及生产环境,那么就可以创建三个Namespace,不同的Namespace之间是互相隔离的。

举个例子,我们可以来创建一个开发环境专用的命名空间:
Spring Cloud Alibaba之服务发现组件 - Nacos

创建完成后可以看到每个命名空间都会有自己的唯一命名空间id:
Spring Cloud Alibaba之服务发现组件 - Nacos

那么微服务要如何注册到指定的命名空间呢?很简单,在配置文件中通过namespace配置项指定即可:

spring:
  cloud:
    nacos:
      discovery:
        # 指定nacos server的地址
        server-addr: 127.0.0.1:8848
        # 配置命名空间
        namespace: 4a557407-c2c8-4524-97a0-17aa3c836407

  ...      

配置完成后重启项目,此时该服务不再是注册到之前的public命名空间,而是注册到我们刚刚创建的dev命名空间,此时在dev下的微服务是看不到public下的微服务的,即实现了环境隔离了:
Spring Cloud Alibaba之服务发现组件 - Nacos


2、在Namespace之下是Group服务分组,默认的Group是DEFAULT_GROUP。我们可以在服务列表中看到:
Spring Cloud Alibaba之服务发现组件 - Nacos

Group可以将不同的微服务划分到同一个分组里,Group可以让我们方便去分组管理微服务。但需要注意的是,在目前的Spring Cloud Alibaba版本,并没有用上这个Group,应该会在未来的版本上支持。


3、而Group下面是Service,Service既是微服务,例如用户微服务、订单微服务等。每个Service可以包含多个Cluster(集群),Cluster是对指定微服务的一个虚拟划分,默认为DEFAULT。比方说公司内有北京机房、上海机房的服务器,那么为了异地容灾,用户中心微服务就可能会被同时部署在北京机房和上海机房。这时候我们就可以为北京机房的用户中心微服务实例划分到一个集群里并且起一个集群名称,而上海机房的用户中心微服务实例同理。将微服务实例划分到一个集群后,我们还可以实现类似于让上海机房里的微服务尽量调用同机房内的其他微服务,这样在容灾的同时还可以提升性能。

关于集群我们可以直接在配置文件中配置,而不用像命名空间那样需要提前创建,如下:

spring:
  cloud:
    nacos:
      discovery:
        # 指定nacos server的地址
        server-addr: 127.0.0.1:8848
        # 配置集群名称,名称可以任意
        cluster-name: BJ

  ...      

配置完成后重启项目,此时可以看到该微服务实例处于BJ集群下,而不是处于之前默认的DEFAULT集群下:
Spring Cloud Alibaba之服务发现组件 - Nacos

4、Cluster里是Instance,即上面所提到的微服务实例

关于这些概念的官方文档地址如下:

https://nacos.io/zh-cn/docs/concepts.html


Nacos元数据

什么是Nacos元数据,官方文档描述如下:

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

  • 服务级别的元信息在服务详情中展示
  • 集群的元信息在集群配置中展示
  • 实例的元信息在集群实例表内展示

元数据作用:

  • 提供描述信息
  • 让微服务调用更灵活
    • 例如实现微服务的版本控制;当有多个版本的微服务共存时就可以通过元数据进行版本控制

配置元数据的两种方式:

  1. 在Nacos Server的管理页面设置;以服务的元数据示例,在服务详情页面点击编辑服务后,在元数据这栏输入json格式的元数据即可:
    Spring Cloud Alibaba之服务发现组件 - Nacos

  2. 通过配置文件指定;如下示例:
    spring:
    cloud:
    nacos:
      discovery:
        # 指定nacos server的地址
        server-addr: 127.0.0.1:8848
        # 这种方式配置的是实例级别的元数据
        metadata:
          # k-v形式,k和v都可以自定义
          instance: c
          test: a
          version: v1

配置完成后重启项目,可以看到配置文件中所配置的实例级别元数据:
Spring Cloud Alibaba之服务发现组件 - Nacos