乐趣区

Spring-Cloud-Alibaba-Nacos

越来越多的读者在和我交流关于 Spring Cloud Alibaba 的种种事宜,甚至于在一次面试中,一半时间都在聊这个话题。所以,本着对技术钻研的热情,对 Spring Cloud Alibaba 进行了一番研究。

在这里,并不想高谈阔论,也不想预言未来,只是挑选了几个从阿里巴巴中间件团队内脱胎出来的开源组件,对于解决实际业务问题有所裨益。

一、背景

先来说说大背景。

现在,很明显的一个趋势就是:微服务

这个趋势的底层驱动力就来源于分布式系统的普及,而微服务的各个特性是如今大大小小的企业无法拒绝的诱惑。

然后,用上了微服务的架构风格,用 Spring Cloud,或者 Dubbo 搭了一套脚手架,就开始干起来了。

接下来,一众小公司画完了大饼之后,发现自己根本吃不下。这就是典型的落后劳动力与先进生产力的尖锐矛盾。这个时候,返璞归真的想法是不能有了,重构代价太大。

当然,哪里有问题,哪里就有商机。各大 XX 云厂商经过一系列包装之后,用“云原生(Cloud Native)”的新概念粉墨登场。

Spring Cloud Alibaba 就是其中之一。

这个概念的一个核心价值就是:平滑上云,赋能运维。最明显的业务表象就是,会提供一套 Open API,甚至是贴心的提供一个可视化控制台,傻瓜式的那种。

二、从 NACOS 说起

这是一颗耀眼的掌上明珠,迅速引起了我的注意。

按照套路,分为两讲。其一讲述 NACOS 的功能特性,及其使用,再者就是更深入一步,看看大厂的攻城狮们写的代码。

本文所使用的版本是NACOS 1.0.0,由于此版本还是第一个 NACOS 正式版,NACOS 正处在飞速发展阶段,本文的一些内容可能会不适用于以后的版本,请读者自行辨别。

NACOS 解决两个核心问题:动态配置管理,服务注册发现。

兼容性方面,除了支持自家的 Dubbo,还对 Spring Cloud,Kubernetes,Istio 有所兼容。

对照以上的全景图,现在的 NACOS 还有一段距离,但是并不遥远。

至此,不说道说道 Eureka,都有点过意不去了。

我用下来的体验是:NACOS 完全可以替代 Eureka 了。

江山代有才人出,这是必然的结果。

在“云原生”的大背景之下,NACOS 顺利成章的推出了 Console,将触角进一步延伸至服务的精细化管理。

当然,不排除 Eureka 也在憋大招。

再说说动态配置的特性。

当然,NACOS 略胜一筹,可替代 Spring Cloud Config 了。

原先在 Git/SVN 上托管的配置项,都可以在 Console 上统一管理了。

如果想先睹为快,可以接下着往下读。如果想再多了解一些,可以直接跳过这部分,阅读下一个小节。

可以把 NACOS 理解成是一个中心化的服务,这在阿里系的架构中屡见不鲜。所以,必须得先启动这个服务。

有两个办法:其一是直接 clone 源码,使用 maven 打包。第二个办法是直接下载 GitHub release 出来的压缩包。

推荐后者。

方法 1 :主要运行以下命令:

git clone https://github.com/alibaba/nacos.git 
cd nacos/ 
mvn -Prelease-nacos clean install -U

经过一段时间的构建过程,在./distribution/target 目录下有我们想要的压缩包。

方法 2 :进入 https://github.com/alibaba/nacos/releases,找到压缩包,下载。

为了演示,我们先用单机模式启动。

Windows 环境下:

startup.cmd -m standalone

一切就绪的话,访问 http://127.0.0.1:8848/nacos/index.html,使用 nacos/nacos 登录。

接下来,随便逛逛。

三、重要的概念

为了避免在 Console 中迷失自我,有必要先阐述几个重要的概念。

这张图很重要。表述了 namespace、group 和 service/dataId 的包含关系。

NACOS 给的最佳实践表明,最外层的 namespace 是可以用于区分部署环境的,比如 test,uat,product 等。同时,也有一个商业利用价值:多租户。以 namespace 为单位,给用户开辟使用空间。

其它两个领域模型不用多解释了,见名知意。其目的也非常明显,就是为了能够逻辑上区分两个目标对象。

默认情况下,namespace=public,group=DEFAULT_GROUP。

明白了这个数据模型后,可以稍微玩转一下 Console 了,比如新建若干个 namespace:

namespace 顺利创建成功后,会在每个一级页面看到由 namespace 组成的 TAB,可以任意切换 namespace,对其下的数据进行操作。比如下图的配置列表:

当然,还有众多的领域模型,但是跟这一讲的关系不大了,下一讲深入源码的时候再进行剖析。接下来会创建一个 Demo 工程,用于构建基于 NACOS 的 config server 和 discovery server。

在进行下一章节之前,强烈建议先创建一个 Spring Boot 样板工程,可以不做任何配置,不添加任何一段代码。

当然,为了让这一讲的内容更具实战意义,我不会照搬官网的 Demo,本文所作的 Demo 工程也可以从 GitHub 上克隆得到。请提前准备好,接下来的内容只会挑重点讲解,侧重点不会在如何搭建工程。

四、使用 NACOS 的搭建工程

工程名称为:nacos-example,在各个 pom.xml 文件中相关依赖管理就不再赘述了。

现在来介绍一下,这个 demo 工程干了一件什么事:用户在下单的时候,需要校验用户的状态,商品是否上架以及购买的数量上限。

为了体现动态配置,用户状态和购买的数量上限做成配置项。而这两者的配置在不同的 namespace 下是有所不同的。

为了体现服务间的调用,下单入口在 order 模块,经由 user 模块进行校验。

根据上述,NACOS 服务启动好了后,我们分别创建 dev,product 两个 namespace,在写 spring boot 配置的时候,填写的不是 test,product 这些 namespace 名称,而是 ID,此 ID 会在 namespace 成功创建后,由系统自动分配,目前的生成规则就是随机的 UUID 函数。

这样的配置规则会存在很大的争议,本文不做讨论。

user 模块需要动态配置,所以可以看作是一个 config client。又因为需要接受 order 模块的调用,所以也是一个 service provider。

同样的,order 模块需要动态配置,所以也是一个 config client。

至此,读者会发现一些 NACOS 带来的“弊病”,最显现的问题就是“多重角色”带来的侵入性。因为是中心化的架构,如果没有做到很好的解耦,出现这个问题也不足为奇了。

特别是在远程调用的角色分配上,NACOS 严格遵从了“生产者 - 消费者”模型,在实际业务场景下,难免会有服务既是生产者,又是消费者,而 Eureka 是没有这种区分的。

接下来把目光转向配置。

阿里系的开源产品大多建议是用 properties 文件,由于个人习惯,我使用的是 yml,达到的效果是一样的。

值得注意的是,因为需要更高优先级的加载顺序,所以配置文件必须使用 bootstrap.yml。

user 模块的配置文件,区分了环境,为了增加辨识度,也定义了 group 进行隔离。如下:

server:
  port: 8100
spring:
  application:
    name: user
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        namespace: 7d3e5f19-a102-471a-b6e0-67bd7d1d35f3
        group: USER_GROUP
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: 7d3e5f19-a102-471a-b6e0-67bd7d1d35f3
---
spring:
  profiles: prod
  cloud:
    nacos:
      config:
        server-addr: 11.162.196.16:8848
        namespace: c4c81555-91e1-4ef5-8b57-77c5407b3481
      discovery:
        server-addr: 11.162.196.16:8848
        namespace: c4c81555-91e1-4ef5-8b57-77c5407b3481
---
spring:
  profiles:
    active: dev

order 模块的配置文件类似,不再赘述。

然后对自定义配置参数进行封装。如果只有单个参数,可以不用 JavaBean 的形式,但是绝大多数情况下都是多参数配置的,本文给出了 JavaBean 形式的封装。

user 模块使用到的配置参数:

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Getter
@Setter
@ConfigurationProperties(prefix = "user")
public class UserConfig {

    /**
     * 用户状态:enable- 启用,disable- 禁用
     */
    private String status;
}

order 模块使用到的配置参数:

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Getter
@Setter
@ConfigurationProperties(prefix = "order")
public class OrderConfig {

    /**
     * 最大购买数量
     */
    private int maxNum = Integer.MAX_VALUE;

    /**
     * 是否上架
     */
    private boolean onSale = true;
}

然后就是编写功能实现代码,主要入口在 order 模块中的 OrderController:

@RefreshScope
@RestController
@RequestMapping
public class OrderController {

    @Autowired
    private OrderConfig config;

    @Autowired
    private UserRpcService userRpcService;

    @PostMapping(value = "/order")
    public String placeOrder(@RequestParam(name = "num", defaultValue = "1") Integer num) {if (!"enable".equals(userRpcService.getUserStatus())) return "该用户已被禁用,暂不能下单";
        if (num <= 0) return "购买数量有误";
        if (!config.isOnSale()) return "商品未上架";
        if (num > config.getMaxNum()) return "购买数量超限";
        return "OK";
    }
}

一些列的下单校验,第一个校验是判断用户状态,使用了基于 Feign 的远程调用。当然,必须在 user 模块中提供对应的 Controller 实现:

@RefreshScope
@RestController
@RequestMapping(value = "/user")
public class UserController {

    @Autowired
    private UserConfig config;

    @GetMapping(value = "/status")
    public String getUserStatus() {return config.getStatus();
    }
}

Controller 中的 @RefreshScope 注解就是用来实现配置自动更新的。

整个工程准备就绪之后,先别急着 run,还需要在 Console 中添加配置,用以测试动态配置是否生效。

添加配置的过程不再赘述,需要注意的是 Data Id 的填写规范。dataId 的完整格式由三部分组成:

${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 来配置。目前只支持 properties 和 yaml 类型。

接下来就是分别启动 order 和 user 两个模块,随便玩玩。

五、其他功能简介

NACOS 会记录配置文件的历史版本,保留 30 天,同时还贴心的提供了一键回滚功能,回滚操作将会触发配置更新。

NACOS 提供了配置监听的 Open-API。注册监听采用的是异步 Servlet 技术。注册监听本质就是带着配置和配置值的 MD5 值和后台对比。如果 MD5 值不一致,就立即返回不一致的配置。如果值一致,就等待一定时间段,返回空值。

最后就是优雅上 / 下线功能,算是 NACOS 的一大亮点。在每个服务详情里面,可以有多个实例(instance),通过 Console 可以控制每个实例的上 / 下线。如果是重新上线的话,会有一段时间的注册过程,并不是点击“上线”按钮之后就能立马访问到该实例。

六、NACOS 的进阶功能

如果决定要将 NACOS 大规模投产,上述功能还远远满足不了条件。在这一章节,将会讲解若干个进阶功能,为你的 NACOS 服务保驾护航。

1、集群模式

只启动一个 NACOS 服务实例未免有些势单力薄了,前述内容都是在单机部署的前提下,这一小节教你如何部署一个 NACOS 集群。

找到 conf/cluster.conf 文件,如果没有,就新建。编辑里面的内容,仅需指定机器的 ip 和 port,最好是 3 个或者 3 个以上。如果没有这么多机器资源,可以直接在一台机器上部署,区分端口号即可。如下:

# ip:port
192.168.0.88:8848
192.168.0.88:8849
192.168.0.88:8840

请注意,在集群模式下就不能用 127.0.0.1 或者 localhost 了,当前版本对于网卡解析存在 bug。

这个问题是因为 Nacos 获取本机 IP 时,没有获取到正确的外部 IP,需要保证 InetAddress.getLocalHost().getHostAddress()或者 hostname - i 的结果是与 cluster.conf 里配置的 IP 是一致的。

找到 conf/application.properties,如果没有,说明有问题。编辑里面的内容,主要目的是启用 MySQL 作为存储层(目前仅支持 MySQL)。在该文件末尾追加:

spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456

然后新建一个名为 nacos 的数据库,运行 conf/nacos-mysql.sql 脚本文件。

以上,就是运行 NACOS 集群模式所需的配置内容。

因为要在一台机器上模拟多个 NACOS 服务,可以将配置好的工程复制出两份,然后在 conf/application.properties 中修改端口号即可。

然后,分别在三个命令窗口启动 NACOS:

startup -m cluster

分别使用对应的端口号访问 Console,如果能顺利打开,说明部署成功了。

最后,在上述搭建的 demo 工程中,修改 server-addr 的值,把其他 NACOS 访问地址也加上去:

server:
  port: 8200
spring:
  application:
    name: order
  cloud:
    nacos:
      config:
        server-addr: 192.168.0.88:8848,192.168.0.88:8849,192.168.0.88:8840
        file-extension: yaml
        namespace: 7d3e5f19-a102-471a-b6e0-67bd7d1d35f3
        group: ORDER_GROUP
      discovery:
        server-addr: 192.168.0.88:8848,192.168.0.88:8849,192.168.0.88:8840
        namespace: 7d3e5f19-a102-471a-b6e0-67bd7d1d35f3

2、安全措施

很遗憾,NACOS 的官网并没有就安全性进行详细的介绍。唯一看到的 accessKeysecretKey两项疑似安全的配置项,却是为了配合阿里云的 ACM 而设立的。

让我最为关注的配置文件加密功能也暂未 release 出来,以下是官方原话:

Nacos 计划在 1.X 版本提供加密的能力,目前还不支持加密,只能靠 sdk 做好了加密再存到 nacos 中。
NACOS Console 的安全性同样很糟糕,密码无法修改,用户不能创建,也没有角色、权限的概念。

NACOS 的服务注册与发现基于 HttpURLConnection 进行远程调用,这个地方是提供了 HTTPS 的启用开关,JVM 参数名是:com.alibaba.nacos.client.naming.tls.enable

除此之外,目前只能在 NACOS 的外围做一些安全措施了。

3、一些有用的配置

nacos.home,这个是 NACOS 服务的启动参数,以 JVM 参数的形式传入。但是很遗憾,使用这个参数并不是很方便,需要修改启动脚本,大概是在 startup.sh 文件中的 104 行:

JAVA_OPT="${JAVA_OPT} -Dnacos.home=${BASE_DIR}"

更为彻底的方法是修改 BASE_DIR 的值(默认为 startup.sh 文件的父目录),大概在 startup.sh 文件中的 71 行:

export BASE_DIR=`cd $(dirname $0)/..; pwd`

nacos.logging.path,日志是一个系统运行最宝贵的附加产物,有的时候为了便于管理,需要自定义日志目录。如果需要指定,同样需要修改 startup.sh 脚本。大概在 104 行后追加一行:

JAVA_OPT="${JAVA_OPT} -Dnacos.logging.path= 指定的目录"

当然,日志级别也能指定,分别通过 com.alibaba.nacos.naming.log.level 和 com.alibaba.nacos.config.log.level 这两个 JVM 参数指定。

不过,还需要特别说明一下,并非所有日志都是有价值的,可以根据实际情况进行删减,否则会对服务器造成不必要的负担,相关日志配置可以在 conf/nacos-logback.xml 文件中修改。

4、endpoint

在上述配置文件中所涉及的 serverAddr 参数是穷举了所有可用的 NACOS 服务,在大规模应用场景下,这种配置方式显然是不合理的。endpoint 相当于一个 DNS 服务,可以代理所有 NACOS 服务,只需要指定一个访问域名。

这篇文章是对 endpoint 的一个最佳实践,并且给出了环境隔离的有效手段。

七、总结

本文是对 Spring Cloud Alibaba Nacos 的功能性介绍。

如果研发能力强劲的队伍,可以现在尝尝这只“螃蟹”,顺带还能贡献不少的 PR。

个人建议先观望一段时间,大概在 v1.2.0 版本之后,就可以逐渐引入到公司的技术栈内了。

退出移动版