环境:Nacos 1.4.2
什么是Nacos
一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
通俗解释就是:Nacos是一个注册中心&配置中心
关键特性
- 服务发现和服务健康监测
- 动态配置服务
- 动态 DNS 服务
- 服务及其元数据管理
更多详细内容请关注Nacos官方文档
什么是注册中心
我们说Nacos是一个注册中心,那么,什么是注册中心呢?通过上面的关键特性不难发现,注册中心提供了服务注册,服务发现,服务健康检查等基础能力,使开发者能够轻松地管理服务,在Spring Cloud家族中,代表性的有Spring Cloud Eureka, Spring Cloud Consul,以及早期Apache的Zookeeper, 这些都能称之为注册中心,如果之前没有接触过微服务的小伙伴可能有疑问,为什么需要注册中心呢?
注册中心的由来
我们先来看看最原始的服务与服务之间的调用方式
Http远程调用
相信这是最简单的也是大家最熟悉的调用方式,在同一个公司中,A项目组(简称订单服务)想要使用B项目组(简称商品服务)提供的能力时,往往就会采用这样的方式,只需简单的发送一个Http请求,即能获取到对方的能力。
这样的方式比较简单,但同样有一些问题,比如:
- 商品服务做服务迁移时,订单服务将不得不跟随着修改请求地址,然后重启服务
- 商品服务做了集群时,订单服务需要有负载均衡策略,不然就会造成商品服务单节点压力过大
这时,有开发经验的小伙伴就会说:搞个Nginx做负载均衡不就好啦!没错,当服务上到生产环境时,我们都会改成这样的方式。
Nginx负载
基于Http请求直接调用的问题,我们只需稍稍改变一下调用策略:中间增加一个Nginx,订单服务不再直接调用商品服务,而且访问Nginx,由Nginx进行转发到商品服务上,此时,
如果商品服务ip地址发生变更:改动Nginx的代理配置即可
如果商品服务发生扩缩容:改动Nginx的负载均衡配置即可
完美解决Http请求直接调用的问题
一般来说,寻常的项目到这里就可以了,但是随着业务的不断迭代,需要的能力也逐渐增多,此时不仅仅只有订单和商品服务,还出现了物流,积分,库存,支付等等服务,不仅如此,这些服务与服务之间还会发生相互调用的情况,订单调积分,物流调库存,订单调支付....此时服务与服务之间的调用情况就会像下面这样:
不难想象,此时的Nginx配置将会打动每一位运维小哥。后续每一个服务配置发生改动,或者增加一个服务,运维小哥的心都将颤一颤。
直到有一天,开发:今天再加一个服务。
阿鉴在之前的公司就有幸目睹过运维小哥配置服务时nginx的样子,三台nginx的配置满满当当,令人头皮发麻,不知道有没有小伙伴遇到过这种情况呢
使用MySQL记录地址信息
使用Nginx负载的方式确实是一个比较好的方式,但就是有些费运维小哥,我们不妨思考一下,除了运维小哥知道服务的地址信息,还有谁知道呢?
没错,就是服务本身!
我们将运维小哥配置服务地址的动作转化成:服务启动时向MySQL中插入一条数据。
Nginx路由转发的动作改为:订单服务到数据库中查询商品服务的地址,再发起调用。
Nginx负载均衡改为:订单服务查询出商品服务列表,自己选择其中一个地址。
改变为的方式如下图:
表结构中有个server_name字段,调用商品服务时使用server_name='goods-server'为条件查询即可。
这样子,我们就可以把运维小哥解放(kaichu)了。
善于思考的小伙伴不难看出其中带来的问题:
-
所有的服务都需要连接这个MySQL,一但MySQL跪了,那咱也跪了。
-
每次服务发起调用时都需要查询一次MySQL。
-
服务如果挂了,MySQL中的记录仍旧存在。
那么我们现在就来着手解决这些问题吧
- 既然MySQL不行,我们就把它换成Redis,所有服务连一个MySQL不太现实,连Redis总行吧
- 每次调用前都要查询一次,改成查询后缓存到服务本地,同时启动定时任务,每30秒更新缓存,服务调用时从缓存中获取地址信息。
- 服务如果挂了,MySQL中的记录仍旧存在:插入到Redis的数据有过期时间,而且服务启动后开启定时任务,每30秒就更新缓存,如果服务挂了,缓存自动过期。
嘿,现在在看这个方案,是不是比较完美了呢~
偷偷告诉小伙伴,Dubbo使用Redis做注册中心时,就是这么玩的
虽然看起来完美,但是第二和第三点在真正使用时,产生的问题产生的足以让人自闭,现在阿鉴给大家模拟一下自闭场景。
环境:订单服务:一个实例,商品服务,两个实例,A和B。
时间:1分00秒
订单服务调用商品服务,正常。
A商品服务缓存过期时间剩余:29S
时间:1分01秒
A商品服务同步Redis缓存信息,Redis剩余过期时间:30S。
同时,A商品服务下线(挂了)
时间:1分02秒
订单服务调用商品服务,由于A商品服务已下线,此时的调用时好时坏(调用B时正常)。
A商品服务在本地缓存过期时间剩余:28S
A商品服务Redis剩余过期时间: 29S
时间:1分29秒
本地缓存更新,刷新A商品服务缓存时间:30S
A商品服务Redis剩余过期时间: 1S,下一秒过期
时间:1分59秒
本地缓存更新,刷新A商品服务缓存:此时Redis缓存中已没有A商品服务信息,所以本地缓存也将不再存在A商品服务信息。
订单服务调用商品服务,正常(此时缓存里只有B商品服务信息)
模拟完毕
大家可以看到,在1分01秒时,A商品服务已经下线了,但是直到1分59秒时,订单服务才能反应过来,这其中的58秒的服务调用时好时坏,怎么样,要是在生产环境足够让人自闭了吧。
当然,如果在订单服务上加上一个重试策略:在调用A商品服务失败后,重试,调用B商品服务,就能较好的解决这个问题,但是假设A商品服务没挂,它只是抖了一下,订单服务又去调用了B商品服务,这等于一个接口同时调了两次,这时又要考虑接口幂等性的问题了。
这时可能有小伙伴会说:幂等性的问题有啥的,Easy! 但一个项目的开发人员水平参差不齐,要求人人都做到这是不太现实的,不说了,说多了该被喷了。
这个问题的关键在于:被调用的服务挂了,调用方能不能及时感知到这个变化。
嘿,最早的Zookeeper(简称zk)就是来干这活的~ zk的临时节点机制能使服务下线后,zk能够立即感知,watch机制能够使其他服务立即收到所监听数据的变化。过多内容阿鉴这里就不再展开说了,有兴趣的小伙伴可以自己研究一下,Zookeeper官网
注册中心
聊了那么多,相信小伙伴已经有些感觉什么是注册中心了哈,没错,以上都是注册中心的能力,当我们把这些能力进行抽象,做成了一个服务时,那么这个服务,就叫做注册中心!
下面这张阿鉴画的图还是有些好看哈
可以看到,注册中心的基础接口有4个:服务注册,服务发现(获取),心跳检测,服务注销
以下为使用注册中心时服务的调用过程
- 订单服务|商品服务启动时,调用服务注册接口,将自己的地址信息发送给注册中心
- 订单服务调用商品服务时,调用服务获取接口,获取到商品服务的地址信息,放入缓存
- 使用调用组件进行负载均衡,发起调用
- 商品服务下线,调用服务注销接口,注册中心剔除商品服务信息
当然,这样的方式还是没能解决
被调用的服务挂了,调用方能不能及时感知到这个变化
这个问题,到底该如何解决,我们放到后面再聊~
相信小伙伴已经明白了注册中心的原理,那么现在我们就来手写一个注册中心吧,开始使用Nacos吧。
Nacos安装
Nacos支持三种部署模式
- 单机模式 - 用于测试和单机试用。
- 集群模式 - 用于生产环境,确保高可用。
- 多集群模式 - 用于多数据中心场景。
单机模式
环境:Ubuntu 18.04 LTS 一台
下载安装包
这里选择版本1.4.2
wget https://github.wuyanzheshui.workers.dev/alibaba/nacos/releases/download/1.4.2/nacos-server-1.4.2.tar.gz
tar -xf nacos-server-1.4.2.tar.gz
配置
Nacos支持两种数据存储方式
- 使用内置数据源实现数据的存储,使用这种方式直接启动Nacos即可
- 使用MySQL进行数据存储,这种方式需要先有个MySQL
为了方便观察数据存储的基本情况,这里我们使用MySQL的存储方式。
变变变,变出一台MySQL,没有MySQL的小伙伴可以参考安装MySQL
-
执行初始化SQL脚本
-
修改配置文件
vim nacos/conf/application.properties
spring.datasource.platform=mysql ### Count of DB: db.num=1 ### Connect URL of DB: db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC db.user.0=nacos db.password.0=nacos nacos.core.auth.enabled=true nacos.core.auth.enable.userAgentAuthWhite=false nacos.core.auth.server.identity.key=abcd nacos.core.auth.server.identity.value=123456
其中db相关配置修改为自己的MySQL配置,
nacos.core.auth.server.identity.key
和nacos.core.auth.server.identity.value
自行定义
启动
bash nacos/bin/startup.sh -m standalone
ubuntu系统需要使用bash命令,其他系统使用sh命令即可
此时在浏览器中输入以下地址即可打开Nacos控制台
127.0.0.1:8848/nacos/index.html
初始账号密码为:nacos | nacos
集群模式
环境:Ubuntu 18.04 LTS 三台,地址分别为:
192.168.2.11:8848
192.168.2.12:8848
192.168.2.13:8848
没有三台的小伙伴可以搭建一个伪集群用于测试,如127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
集群模式搭建十分简单,具体步骤如下:
-
将单机模式的配置复制三份到每个节点中
-
在每个节点的conf文件夹下新建
cluster.conf
,配置如下192.168.2.11:8848 192.168.2.12:8848 192.168.2.13:8848
-
依次启动各个节点
bash startup.sh
这样子nacos集群就搭建好了,但是在浏览器访问时我们还需要一个nginx做负载均衡
变变变,变成一个nginx,如果没有nginx的小伙伴可以参考安装Nginx
- 配置反向代理
server {
listen 8850;
server_name _;
location / {
proxy_pass http://nacos-server;
}
}
upstream nacos-server {
server 192.168.2.13:8848 weight=5;
server 192.168.2.12:8848 weight=5;
server 192.168.2.11:8848 weight=5;
}
-
访问
由于我的Nginx在192.168.2.11上,所以我的访问地址为:192.168.2.11:8848/nacos,初始账号密码同单机模式:nacos | nacos
Nacos Spring Cloud
Nacos已经搭建好了,接下来就是使用了。
下面阿鉴给大家介绍一下如何在Spring Cloud项目中使用Nacos
基本使用
新建项目
- 新建一个
my-micro-service-demo
项目,并在pom.xml
中增加Spring Cloud相关依赖
<properties>
<java.version>1.8</java.version>
<spring.cloud-version>Hoxton.SR8</spring.cloud-version>
<spring.cloud.alibaba-version>2.2.5.RELEASE</spring.cloud.alibaba-version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 在项目中增加两个子模块,分别为
my-order
和my-goods
, 并引入Nacos的依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 编写
bootstrap.yaml
配置文件
server:
port: 8080
spring:
application:
name: my-order
main:
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
server-addr: 192.168.2.11:8850
username: nacos
password: nacos
management:
endpoints:
web:
exposure:
include: "*"
商品服务修改一下服务名和端口号即可
- 编写启动类
package com.my.micro.service.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Zijian Liao
* @since 1.0.0
*/
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
-
启动项目,查看Nacos控制台
服务注册成功
发起调用
服务已经注册到Nacos上了,接下来,我们尝试使用订单服务调用商品服务吧
在画的原理图中,订单服务是使用的一个调用组件
调用商品服务,大家先不必关心这个调用组件是什么,关于这部分内容,阿鉴会在后续的文章中详解介绍。
-
在商品服务中编写一个接口
@RestController @RequestMapping("/goods") public class GoodsController { @GetMapping("/get") public String get(){ return "商品服务响应了一个苹果"; } }
-
在订单服务中注入调用组件
@LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate(); }
增加@LoadBalanced注解会使RestTemplate增加基于服务名进行调用的功能
-
发起调用
@RestController @RequestMapping("/order") public class OrderController { @Resource private RestTemplate restTemplate; @GetMapping("/get") public String get(){ // 旧方式:restTemplate.getForObject("http://127.0.0.1:8081/goods/get", String.class) // 现在直接使用服务名即可发起调用 return restTemplate.getForObject("http://my-goods/goods/get", String.class); } }
-
测试
优雅上下线
Nacos还提供服务实例的上下线操作,在服务详情页面,可以点击实例的上线
或者下线
按钮,被下线的实例,将不会再被服务所获取。
当然,前面也提到了,由于缓存的原因,服务下线后,调用方并不能立即感知到,需要等待一定的时间。
那么,这个功能可以用来做什么呢?
假设商品服务需要升级
运行版本:v1.0.0
升级版本:v1.1.0
我们先将v1.1.0部署到生产环境中,此时商品服务便有了两个实例v1.0.0和v1.1.0,紧接着我们需要将旧版本下线
如果我们直接停止服务,此时商品服务正在处理业务,业务被中断,造成的后果不可估摸。
如果我们先将v1.0.0下线,等待一段时间,此时便不会有服务再访问v1.0.0了,便可以安全停止服务了。
命名空间
在以往的注册中心里,一般都是一个注册中心只对应一整个微服务系统,这是因为只要有服务注册到了注册中心上,那么只要在这个注册中心上的服务,都是可以相互访问的,这样就造成了每一个微服务系统,都要有一个自己的注册中心,比如Eureka。
这就会造成一个资源浪费的问题,因为一个注册中心明明是可以注册成千上万的实例的(Nacos服务发现性能测试报告),所以,Nacos还给我们提供了命名空间的功能,这使得服务与服务之间拥有了隔离机制(同一个命名空间下的服务才能相互访问),这样,每个命名空间便可以对应一个微服务系统。
-
新增命名空间
新增一个
dev
命名空间与test
命名空间 -
修改服务配置
修改my-goods, 让my-goods处于dev命名空间下
server: port: 8081 spring: application: name: my-goods main: allow-bean-definition-overriding: true cloud: nacos: discovery: server-addr: 192.168.2.11:8850 namespace: dev username: nacos password: nacos
修改my-order, 让my-order处于test命名空间下
server: port: 8080 spring: application: name: my-order main: allow-bean-definition-overriding: true cloud: nacos: discovery: server-addr: 192.168.2.11:8850 namespace: test username: nacos password: nacos
-
重启服务
查看Nacos控制台
my-goods
my-order
-
测试
此时服务已经隔离了
小结
关于注册中心和Nacos的基本知识已经介绍完了,现在我们来总结一下吧。
首先,注册中心是一个具备了服务注册,服务发现,服务健康检查等能力的服务,通过它我们能够轻松地对服务进行管理,在此之上,Nacos还实现了优雅上下线,命名空间等功能,最后,我们还通过一个案例对这些功能进行了演示。
下篇,阿鉴会给大家介绍Nacos的另一项能力:配置中心。