Spring Cloud(十六):Alibaba 之 Nacos

上个月最后一天的凌晨,Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器,并在 maven 中央库发布了第一个版本。

目前 Spring Cloud Alibaba 还只能算是预览版吧,里边的坑肯定不少,不过我还是决定试试,看看 Alibaba 到底靠谱不靠谱。

Nacos for Spring Cloud

Spring Cloud Alibaba

目前 Spring Cloud Alibaba 项目还处于 Spring Cloud 官方孵化器中,打开它 Github 的就能看到“亲切”的中文文档

它目前只有三个组件:

  • Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

  • Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

  • AliCloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。

看官方路线图上讲后边还会增加

  • Dubbo:Apache Dubbo™ (incubating) 是一款高性能 Java RPC 框架。

  • RocketMQ:Apache RocketMQ™ 基于 Java 的高性能、高吞吐量的分布式消息和流计算平台。

  • Schedulerx:阿里中间件团队开发的一款分布式任务调度产品,支持周期性的任务与固定时间点触发任务。

  • AliCloud SLS:针对日志类数据的一站式服务,在阿里巴巴集团经历大量大数据场景锤炼而成。您无需开发就能快捷完成日志数据采集、消费、投递以及查询分析等功能,提升运维、运营效率,建立 DT 时代海量日志处理能力。

从数量上来看,Alibaba 的组件数量和目前 Netflix 的相比少了一多半,但是仔细看看各组件的功能描述,也就明白了。在没真正上手之前,我个人先大胆猜测一下:

  • Nacos = Eureka/Consule + Config + Admin
  • Sentinel = Hystrix + Dashboard + Turbine
  • Dubbo = Ribbon + Feign
  • RocketMQ = RabbitMQ
  • Schedulerx = Quartz
  • AliCloud OSS、AliCloud SLS 这三个应该是独有的
  • 链路跟踪(Sleuth、Zipkin)不知道会不会在 Sentinel 里

以上只是猜测,待我从坑里爬出来之后再回来更新。也欢迎大家一起交流探讨~

这里我就先试试 Nacos。

Nacos

nacos arch

这是 Nacos 的架构图,可以看到它确实是融合了服务注册发现中心、配置中心、服务管理等功能,和我之前猜想的它是 Eureka/Consule + Config + Admin 的合体差不多。

另外通过官方文档发现,Nacos 除了可以和 Spring Cloud 集成,还可以和 Spring、SpringBoot 进行集成。

不过我们只关注于 Spring Cloud,别的就略过了,直接上手吧~

工程的目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
alibaba
├── nacos-config
│   ├── pom.xml
│   └── src
├── nacos-consumer
│   ├── pom.xml
│   └── src
├── nacos-provider
│   ├── pom.xml
│   └── src
└── pom.xml

首先引入 Spring Cloud Alibaba 的 BOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/>
</parent>
<properties>
<spring-cloud.version>Finchley.SR2</spring-cloud.version>
<spring-cloud-alibaba.version>0.2.0.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

这里版本号有坑,文档上说和 Spring Boot 2.0.x 版本兼容,但是实测 2.0.6.RELEASE 报错

1
java.lang.NoClassDefFoundError: org/springframework/core/env/EnvironmentCapable

Nacos Server

在使用 Nacos 之前,需要先下载 Nacos 并启动 Nacos Server。

Nacos Server 有两种运行模式:

  • standalone
  • cluster

不论哪种方式吧,都需要先去 https://github.com/alibaba/nacos/releases 下载最新的 release 包,然后解压,以 nacos-server-0.4.0.zip 为例

1
2
unzip nacos-server-0.4.0.zip
cd nacos

standalone 模式

此模式一般用于 demo 和测试,不用改任何配置,直接敲以下命令执行

1
sh bin/startup.sh -m standalone

Windows 的话就是

1
cmd bin/startup.cmd -m standalone

然后从 http://localhost:8848/nacos/index.html 进入控制台就能看到如下界面了

Nacos Console

cluster 模式

集群模式需要依赖 MySQL,然后改两个配置文件:

  • conf/cluster.conf
  • conf/application.properties

具体怎么改,在这里就先不展开了。我们先用 standalone 模式撸起来,享受 coding 的快感,然后再慢慢转到 cluster 上边。

配置管理

  1. 在 nacos/pom.xml 里添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    </dependencies>
  2. 启动类不用修改

    1
    2
    3
    4
    5
    6
    @SpringBootApplication
    public class NacosApplication {
    public static void main(String[] args) {
    SpringApplication.run(NacosApplication.class, args);
    }
    }
  3. 修改 bootstrap.yml

    1
    2
    3
    4
    5
    6
    7
    spring:
    application:
    name: nacos
    cloud:
    nacos:
    config:
    server-addr: 127.0.0.1:8848

    注意:必须是写在 bootstrap.yml 中,配置在 application.yml 中不行,启动报错

    1
    java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.application.name' in value "${spring.application.name}"

    至于 bootstrap.yml 和 application.yml 的区别,之前讲过这里就不赘述了。

  4. 添加一个 Endpoint 便于观察

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @RestController
    @RequestMapping("/config")
    @RefreshScope
    public class ConfigController {

    @Value("${useLocalCache:false}")
    private boolean useLocalCache;

    @RequestMapping("/get")
    public boolean get() {
    return useLocalCache;
    }

    }

    注意一定要加@RefreshScope注解

  5. 小心!此处有坑!

    这时候先别急着启动 NacosConfigApplication 的,需要需要通过调用 Nacos Open API 往 Nacos Server 里发布一个配置。dataId 为 nacos.properties,内容为useLocalCache=true

    1
    2
    3
    4
    5
    curl -X "POST" "http://127.0.0.1:8848/nacos/v1/cs/configs" \
    -H 'Content-Type: application/x-www-form-urlencoded; charset=utf-8' \
    --data-urlencode "dataId=nacos.properties" \
    --data-urlencode "group=DEFAULT_GROUP" \
    --data-urlencode "content=useLocalCache=true"

    这里涉及一个 dataId 的概念

    dataId 的完整格式如下:

    1
    2
    > ${prefix}-${spring.profile.active}.${file-extension}
    >
    • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
    • spring.profile.active 即为当前环境对应的 profile,详情可以参考 Spring Boot 文档注意:当 spring.profile.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
    • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 propertiesyaml 类型。
  6. 然后启动 NacosConfigApplication,从启动日志里能看到

    1
    Located property source: CompositePropertySource {name='NACOS', propertySources=[NacosPropertySource {name='nacos.properties'}]}

    如果 propertySources 里边是空的,那抱歉,你掉到坑里边了。 如果你能看到之前发布的 dataId,那恭喜,请求 http://localhost:8080/config/get 就可以看到返回内容 true 了。

  7. 再次调用 Nacos Open API 修改内容为useLocalCache=false

  8. 再次访问 http://localhost:8080/config/get ,此时返回内容为false,说明程序中的useLocalCache值已经被动态更新了。

当然,以上手动调用 Nacos Open API 的方式也可以通过 Nacos Console 的可视化界面来操作

Nacos Console Config

另外我们可以查询配置的历史记录并能快速回滚

Nacos Config History

还能查询到某个配置当前的被监听状态(这里的分页有些 bug)

Nacos 监听查询

数据源

经过了上边的一些简单操作,我们已经可以正常使用 Nacos 配置中心了。
但是不知道你有没有想过:配置数据是存在哪里呢?

我们没有对 Nacos Server 做任何配置,那么数据只有两个位置可以存储:

  • 内存
  • 本地数据库

如果我们现在重启刚刚在运行的 Nacos Server,会发现刚才加的 nacos.properties 配置还在,说明不是内存存储的。

这时候我们打开NACOS_PATH/data,会发现里边有个derby-data目录,Derby 是 Java 编写的数据库,属于 Apache 的一个开源项目。我们的配置数据现在就存储在这个库中。

Derby 我并不是很熟悉,那能不能将数据源改为我们熟悉的 MySQL 呢?当然可以了。

注意:不支持 MySQL 8.0 版本

这里我以本地运行的 MySQL 为例:

  1. 创建一个名为nacos_config的 database

  2. NACOS_PATH/conf/nacos-mysql.sql中的表结构导入刚才创建的库中,这几张表的用途就自己研究吧

  3. 修改NACOS_PATH/conf/application.properties,加入 MySQL 配置

    1
    2
    3
    4
    db.num=1
    db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
    db.user=root
    db.password=root
  4. 创建cluster.conf,填入要运行 Nacos Server 机器的 ip

    1
    2
    192.168.100.155
    192.168.100.156

    我就是运行个 demo,没有多余机器来组建集群怎么办呢?

    其实不用虚拟机,直接只填一个本地地址也是可以的(仅限于配置管理,服务发现不行)

这里有两个坑:

  • Nacos Server 的数据源是用 Derby 还是 MySQL 完全是由其运行模式决定的:

    • standalone 的话仅会使用 Derby,即使在 application.properties 里边配置 MySQL 也照样无视;
    • cluster 模式会自动使用 MySQL,这时候如果没有 MySQL 的配置,是会报错的。
  • 官方提供的 cluster.conf 示例如下

    1
    2
    3
    4
    5
    #it is ip
    #example
    10.10.109.214
    11.16.128.34
    11.16.128.36

    从习惯来看,这个#号后边的应该就是注释的,但是抱歉哦,必须删掉,否则下面的异常就扑面而来

    1
    2
    3
    4
    5
    6
    7
    8
    Caused by: java.lang.NumberFormatException: For input string: "it is ip:0"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Long.parseLong(Long.java:589)
    at java.lang.Long.parseLong(Long.java:631)
    at com.alibaba.nacos.naming.core.DistroMapper.onServerStatusUpdate(DistroMapper.java:125)
    at com.alibaba.nacos.naming.core.DistroMapper.init(DistroMapper.java:100)
    at com.alibaba.nacos.naming.core.DistroMapper.<clinit>(DistroMapper.java:65)
    ... 79 common frames omitted

以上配置结束后,运行 Nacos Server 就能看到效果了。

除了 MySQL 的数据表发生了变化,我们会发现NACOS_PATH/data下的目录结构也发生了变化,多了config-data/DEFAULT_GROUP/nacos_config这么一个文件,里边的内容就是我们的配置

1
useLocalCache=true

这是容错呢? 还是缓存呢?只有等看过源码才知道了。

服务发现

服务注册中心和服务发现的服务端都是由 Nacos Server 来提供的,我们只需要提供 Service 向其注册就好了。

首先我们先将 Nacos Server 由伪分布式改为 standalone 模式,原因后边再说吧。

这里模拟提供两个 service:provider 和 consumer

1
2
3
4
5
6
7
8
alibaba
├── nacos-provider
│   ├── pom.xml
│   └── src
└── nacos-consumer
│   ├── pom.xml
│   └── src
└── pom.xml
  1. 首先在 provider 和 consumer 的 pom 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
  2. 在两者的 bootstrap.yml 中添加配置

    provider

    1
    2
    3
    4
    5
    6
    7
    8
    9
    spring:
    application:
    name: nacos-provider
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848
    server:
    port: 18080

    consumer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    spring:
    application:
    name: nacos-consumer
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848
    server:
    port: 18081
  3. 使用 Spring Cloud 的原生注解 @EnableDiscoveryClient 开启服务发现

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableDiscoveryClient
    public class NacosProviderApplication {
    public static void main(String[] args) {
    SpringApplication.run(NacosProviderApplication.class, args);
    }
    }
  4. 提供 Endpoint 以供访问

    1. Provider

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @RestController
      @RequestMapping("/echo")
      public class EchoController {

      @RequestMapping(value = "/{string}", method = RequestMethod.GET)
      public String echo(@PathVariable String string) {
      return "Hello Nacos Discovery " + string;
      }

      }
    2. Consumer

      在 NacosConsumerApplication 中集成 RestTemplateRibbon

      1
      2
      3
      4
      5
      @LoadBalanced
      @Bean
      public RestTemplate restTemplate() {
      return new RestTemplate();
      }

      提供 Controller

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      @RestController
      @RequestMapping("/echo")
      public class TestController {

      private final RestTemplate restTemplate;

      @Autowired
      public TestController(RestTemplate restTemplate) {
      this.restTemplate = restTemplate;
      }

      @RequestMapping(value = "/{str}", method = RequestMethod.GET)
      public String echo(@PathVariable String str) {
      return restTemplate.getForObject("http://nacos-provider/echo/" + str, String.class);
      }

      }
  5. 分别启动 NacosProviderApplicationNacosConsumerApplication ,调用 http://localhost:18080/echo/windmthttp://localhost:18081/echo/windmt ,返回内容均为 Hello Nacos Discovery windmt,说明服务发现成功了。

这时候查看 Nacos Console 也能看到已注册的服务列表及其详情

Nacos 服务列表

Nacos 服务详情

现在来讲一下为什么前边要将 Nacos Server 由伪分布式再改为 standalone 模式。

单个节点的 Nacos Server 伪分布式在配置管理运行的好好的,但是到了服务发现,它就失效了。

通过 log 可以发现一些端倪,单节点的在选主的时候,无法正确选出 leader

1
2
==> logs/naming-raft.log <==
2018-11-13 16:38:56,424 INFO leader timeout, start voting,leader: null, term: 1

从而导致 Client 无法正常注册

1
2
3
4
5
6
java.lang.IllegalStateException: failed to req API:/nacos/v1/ns/instance after all servers([127.0.0.1:8848]) tried
at com.alibaba.nacos.client.naming.net.NamingProxy.reqAPI(NamingProxy.java:339) ~[nacos-client-0.3.0.jar:na]
at com.alibaba.nacos.client.naming.net.NamingProxy.reqAPI(NamingProxy.java:272) ~[nacos-client-0.3.0.jar:na]
at com.alibaba.nacos.client.naming.net.NamingProxy.registerService(NamingProxy.java:171) ~[nacos-client-0.3.0.jar:na]
at com.alibaba.nacos.client.naming.NacosNamingService.registerInstance(NacosNamingService.java:161) ~[nacos-client-0.3.0.jar:na]
... ...

小结

当今年年初 Dubbo 进入 Apache 孵化器的时候,就有预感阿里要与 Spring Cloud 结缘。只是没想到这么快。

如今 Spring Cloud Alibaba 已经进入了 Spring Cloud 官方孵化器,相信等不了多久也就能正式发布了。虽然大家在生产环境必然还不会这么快速地接入,但是总归是多了一种选择。

而 Nacos 作为微服务核心的服务注册与发现中心,让大家在 Eureka 和 Consule 之外有了新的选择,开箱即用,上手简洁,暂时也没发现有太大的坑。但将配置中心融合也融合进来是好是坏,这个我先按下不表。

总而言之,Spring Cloud Alibaba 的入驻对于 Spring Cloud 生态总归是好的~

本文示例代码:https://github.com/zhaoyibo/spring-cloud-study/tree/master/alibaba

参考

GitHub - spring-cloud-incubator/spring-cloud-alibaba
GitHub - alibaba/nacos)
Nacos Documentation