夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Node基础

  • 《MySQL》学习笔记

  • Midway

  • Nest

    • 开篇词
    • 学习理由
    • nest概念扫盲
    • 快速掌握 nestcli
    • 5种http数据传输方式
    • IoC 解决了什么痛点问题?
    • 如何调试 Nest 项目
    • Provider注入对象
    • 全局模块和生命周期
    • AOP 架构有什么好处?
    • 一网打尽 Nest 全部装饰器
    • Nest如何自定义装饰器
    • Metadata和Reflector
    • ExecutionContext切换上下文
    • Module和Provider的循环依赖处理
    • 如何创建动态模块
    • Nest和Express,fastify
    • Nest的Middleware
    • RxJS和Interceptor
    • 内置Pipe和自定义Pipe
    • ValidationPipe验证post请求参数
    • 如何自定义 Exception Filter
    • 图解串一串 Nest 核心概念
    • 接口如何实现多版本共存
    • Express如何使用multer实现文件上传
    • Nest使用multer实现文件上传
    • 图书管理系统
    • 大文件分片上传
    • 最完美的 OSS 上传方案
    • Nest里如何打印日志
    • 为什么Node里要用Winston打印日志
    • Nest 集成日志框架 Winston
    • 通过Desktop学Docker也太简单了
    • 你的第一个 Dockerfile
    • Nest 项目如何编写 Dockerfile
    • 提升 Dockerfile 水平的 5 个技巧
    • Docker 是怎么实现的
    • 为什么 Node 应用要用 PM2 来跑?
    • 快速入门 MySQL
    • SQL 查询语句的所有语法和函数
    • 一对一、join 查询、级联方式
    • 一对多、多对多关系的表设计
    • 子查询和 EXISTS
    • SQL 综合练习
    • MySQL 的事务和隔离级别
    • MySQL 的视图、存储过程和函数
    • Node 操作 MySQL 的两种方式
    • 快速掌握 TypeORM
    • TypeORM 一对一的映射和关联 CRUD
    • TypeORM 一对多的映射和关联 CRUD
    • TypeORM 多对多的映射和关联 CRUD
    • 在 Nest 里集成 TypeORM
    • TypeORM保存任意层级的关系
    • 生产环境为什么用TypeORM的migration迁移功能
    • Nest 项目里如何使用 TypeORM 迁移
    • 如何动态读取不同环境的配置?
    • 快速入门 Redis
    • 在 Nest 里操作 Redis
    • 为什么不用 cache-manager 操作 Redis
    • 两种登录状态保存方式:JWT、Session
    • Nest 里实现 Session 和 JWT
    • MySQL + TypeORM + JWT 实现登录注册
    • 基于 ACL 实现权限控制
    • 基于 RBAC 实现权限控制
    • access_token和refresh_token实现无感登录
    • 单token无限续期实现登录无感刷新
    • 使用 passport 做身份认证
    • passport 实现 GitHub 三方账号登录
    • passport 实现 Google 三方账号登录
    • 为什么要使用 Docker Compose ?
    • Docker 容器通信的最简单方式:桥接网络
    • Docker 支持重启策略,是否还需要 PM2
    • 快速掌握 Nginx 的 2 大核心用法
    • 基于 Nginx 实现灰度系统
    • 基于 Redis 实现分布式 session
    • Redis + 高德地图,实现附近的充电宝
    • 用 Swagger 自动生成 api 文档
    • 如何灵活创建 DTO
    • class- validator 的内置装饰器,如何自定义装饰器
    • 序列化 Entity,你不需要 VO 对象
    • 手写序列化 Entity 的拦截器
    • 使用 compodoc 生成文档
    • Node 如何发邮件?
    • 实现基于邮箱验证码的登录
    • 基于 sharp 实现 gif 压缩工具
    • 大文件如何实现流式下载?
    • Puppeteer 实现爬虫,爬取 BOSS 直聘全部前端岗位
    • 实现扫二维码登录
    • Nest 的 REPL 模式
    • 实现 Excel 导入导出
    • 如何用代码动态生成 PPT
    • 如何拿到服务器 CPU、内存、磁盘状态
    • Nest 如何实现国际化?
    • 会议室预订系统:需求分析和原型图
    • 会议室预订系统:技术方案和数据库设计
    • 会议室预订系统:用户管理模块--用户注册
    • 会议室预订系统:用户管理模块--配置抽离、登录认证鉴权
    • 会议室预订系统:用户管理模块-- interceptor、修改信息接口
    • 会议室预订系统:用户管理模块--用户列表和分页查询
    • 会议室预订系统:用户管理模块-- swagger 接口文档
    • 会议室预订系统:用户管理模块-- 用户端登录注册页面
    • 会议室预订系统:用户管理模块-- 用户端信息修改页面
    • 会议室预订系统:用户管理模块-- 头像上传
    • 会议室预订系统:用户管理模块-- 管理端用户列表页面
    • 会议室预订系统:用户管理模块-- 管理端信息修改页面
    • 会议室预订系统:会议室管理模块-后端开发
    • 会议室预订系统:会议室管理模块-管理端前端开发
    • 会议室预订系统:会议室管理模块-用户端前端开发
    • 会议室预订系统:预定管理模块-后端开发
    • 会议室预订系统:预定管理模块-管理端前端开发
    • 会议室预订系统:预定管理模块-用户端前端开发
    • 会议室预订系统:统计管理模块-后端开发
    • 会议室预订系统:统计管理模块-前端开发
    • 会议室预订系统:后端项目部署到阿里云
    • 会议室预订系统:前端项目部署到阿里云
    • 会议室预定系统:用 migration 初始化表和数据
    • 会议室预定系统:文件上传 OSS
    • 会议室预定系统:Google 账号登录后端开发
    • 会议室预定系统:Google 账号登录前端开发
    • 会议室预定系统:后端代码优化
    • 会议室预定系统:集成日志框架 winston
    • 会议室预定系统:前端代码优化
    • 会议室预定系统:全部功能测试
    • 会议室预定系统:项目总结
    • Nest 如何创建微服务?
    • Nest 的 Monorepo 和 Library
    • 用 Etcd 实现微服务配置中心和注册中心
      • 总结
    • Nest 集成 Etcd 做注册中心、配置中心
    • 用 Nacos 实现微服务配置中心和注册中心
    • 基于 gRPC 实现跨语言的微服务通信
    • 快速入门 ORM 框架 Prisma
    • Prisma 的全部命令
    • Prisma 的全部 schema 语法
    • Primsa Client 单表 CRUD 的全部 api
    • Prisma Client 多表 CRUD 的全部 api
    • 在 Nest 里集成 Prisma
    • 为什么前端监控系统要用 RabbitMQ?
    • 基于 Redis 实现关注关系
    • 基于 Redis 实现各种排行榜(周榜、月榜、年榜)
    • 考试系统:需求分析
    • 考试系统:技术方案和数据库设计
    • 考试系统:微服务、Lib 拆分
    • 考试系统;用户注册
    • 考试系统:用户登录、修改密码
    • 考试系统:考试微服务
    • 考试系统:登录、注册页面
    • 考试系统:修改密码、试卷列表页面
    • 考试系统:新增试卷、回收站
    • 考试系统:试卷编辑器
    • 考试系统:试卷回显、预览、保存
    • 考试系统:答卷微服务
    • 考试系统:答题页面
    • 考试系统:自动判卷
    • 考试系统:分析微服务、排行榜页面
    • 考试系统:整体测试
    • 考试系统:项目总结
    • 用 Node.js 手写 WebSocket 协议
    • Nest 开发 WebSocket 服务
    • 基于 Socket.io 的 room 实现群聊
    • 聊天室:需求分析和原型图
    • 聊天室:技术选型和数据库设计
    • 聊天室:用户注册
    • 聊天室:用户登录
    • 聊天室:修改密码、修改信息
    • 聊天室:好友列表、发送好友申请
    • 聊天室:创建聊天室、加入群聊
    • 聊天室:登录、注册页面开发
    • 聊天室:修改密码、信息页面开发
    • 聊天室:头像上传
    • 聊天室:好友∕群聊列表页面
    • 聊天室:添加好友弹窗、通知页面
    • 聊天室:聊天功能后端开发
    • 聊天室:聊天功能前端开发
    • 聊天室:一对一聊天
    • 聊天室:创建群聊、进入群聊
    • 聊天室:发送表情、图片、文件
    • 聊天室:收藏
    • 聊天室:全部功能测试
    • 聊天室:项目总结
    • MongoDB 快速入门
    • 使用 mongoose 操作 MongoDB 数据库
    • GraphQL 快速入门
    • Nest 开发 GraphQL 服务:实现 CRUD
    • GraphQL + Primsa + React 实现 TodoList
    • 如何调试 Nest 源码?
  • 其他

  • 服务端
  • Nest
神说要有光
2025-03-10
目录

用 Etcd 实现微服务配置中心和注册中心

微服务架构的系统都会有配置中心和注册中心。

为什么呢?

比如说配置中心:

系统中会有很多微服务,它们会有一些配置信息,比如环境变量、数据库连接信息等。

这些配置信息散落在各个服务中,以配置文件的形式存在。

这样你修改同样的配置需要去各个服务下改下配置文件,然后重启服务。

就很麻烦。

如果有一个服务专门用来集中管理配置信息呢?

这样每个微服务都从这里拿配置,可以统一的修改,并且配置更改后也会通知各个微服务。

这个集中管理配置信息的服务就叫配置中心。

再就是注册中心:

微服务之间会相互依赖,共同完成业务逻辑的处理。

如果某个微服务挂掉了,那所有依赖它的服务就都不能工作了。

为了避免这种情况,我们会通过集群部署的方式,每种微服务部署若干个节点,并且还可能动态增加一些节点。

那么问题来了:

微服务 A 依赖了微服务 B,写代码的时候 B 只有 3 个节点,但跑起来以后,某个节点挂掉了,并且还新增了几个微服务 B 的节点。

这时候微服务 A 怎么知道微服务 B 有哪些节点可用呢?

答案也是需要一个单独的服务来管理,这个服务就是注册中心:

微服务在启动的时候,向注册中心注册,销毁的时候向注册中心注销,并且定时发心跳包来汇报自己的状态。

在查找其他微服务的时候,去注册中心查一下这个服务的所有节点信息,然后再选一个来用,这个叫做服务发现。

这样微服务就可以动态的增删节点而不影响其他微服务了。

微服务架构的后端系统中,都会有这两种服务。

下面是我网上找的几张微服务系统的架构图:

这个:

这个:

或者这个:

可以看到,配置中心和注册中心是必备组件。

但是,虽然这是两种服务,功能确实很类似,完全可以在一个服务里实现。

可以做配置中心、注册中心的中间件还是挺多的,比如 nacos、apollo、etcd 等。

今天我们来学下 etcd 实现注册中心和配置中心。

它其实是一个 key-value 的存储服务。

k8s 就是用它来做的注册中心、配置中心:

我们通过 docker 把它跑起来。

在 docker desktop 搜索 etcd 的镜像,点击 run:

输入容器名,映射 2379 端口到容器内的 2379 端口,设置 ETCD_ROOT_PASSWORD 环境变量,也就是指定 root 的密码。

然后就可以看到 etcd server 的 docker 镜像成功跑起来了:

它带了一个 etcdctl 的命令行工具,可以作为客户端和 etcd server 交互。

常用的命令有这么几个:

etcdctl put key value
etcdctl get key
etcdctl del key
etcdctl watch key
1
2
3
4

就是对 key value 的增删改查和 watch 变动,还是比较容易理解的。

但是现在执行命令要加上 --user、--password 的参数才可以:

etcdctl get --user=root --password=guang key
1

如果不想每次都指定用户名密码,可以设置环境变量:

export ETCDCTL_USER=root
export ETCDCTL_PASSWORD=guang
1
2

这里的 password 就是启动容器的时候指定的那个环境变量:

我们设置几个 key:

etcdctl put /services/a xxxx
etcdctl put /services/b yyyy
1
2

之后可以 get 来查询他们的值:

etcdctl get /services/a
etcdctl get /services/b
1
2

也可以通过 --prefix 查询指定前缀的 key 的值:

etcdctl get --prefix /services 
1

删除也是可以单个删和指定前缀批量删:

etcdctl del /servcies/a
etcdctl del --prefix /services
1
2

这样的 key-value 用来存储 服务名-链接信息,那就是注册中心,用来存储配置信息,那就是配置中心。

我们在 node 里面连接下 etcd 服务试试看:

创建个项目:

mkdir etcd-test
cd etcd-test
npm init -y
1
2
3

安装 etcd 的包:

npm install --save etcd3
1

创建 index.js

使用 etcd 官方提供的 npm 包 etcd3:

const { Etcd3 } = require('etcd3');
const client = new Etcd3({
    hosts: 'http://localhost:2379',
    auth: {
        username: 'root',
        password: 'guang'
    }
});
 
(async () => { 
  const services = await client.get('/services/a').string();
  console.log('service A:', services);

  const allServices = await client.getAll().prefix('/services').keys();
  console.log('all services:', allServices);
 
  const watcher = await client.watch().key('/services/a').create();
  watcher.on('put', (req) => {
    console.log('put', req.value.toString())
  })
  watcher.on('delete', (req) => {
    console.log('delete')
  })
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

get、getAll、watch 这些 api 和 ectdctl 命令行差不多,很容易搞懂。

get(xx) 是查询某个 key 的值。

getAll().prefix(xx).keys() 是查询某个字符串开头的 key。

watch().key(xx).create 则是创建某个 key 的监听器,监听他的 put 和 delete 事件。

我们再 put 几个 key:

etcdctl put /services/a xxx

etcdctl put /services/b yyy
1
2
3

然后执行上面的 node 脚本:

确实取到了 etcd server 中的值。

然后在 etcdctl 里 put 修改下 /services/a 的值:

etcdctl put /services/a zzz
1

在 node 脚本这里收到了通知:

再 del 试下:

etcdctl del /services/a
1

也收到了通知:

这样,在 node 里操作 etcd server 就跑通了。

然后我们封装下配置中心和注册中心的工具函数:

配置中心的实现比较简单,就是直接 put、get、del 对应的 key:

// 保存配置
async function saveConfig(key, value) {
    await client.put(key).value(value);
}

// 读取配置
async function getConfig(key) {
    return await client.get(key).string();
}

// 删除配置
async function deleteConfig(key) {
    await client.delete().key(key);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

使用起来也很简单;

(async function main() {
    await saveConfig('config-key', 'config-value');
    const configValue = await getConfig('config-key');
    console.log('Config value:', configValue);
})();
1
2
3
4
5

你可以在这里存各种数据库连接信息、环境变量等各种配置。

然后是注册中心:

服务注册:

// 服务注册
async function registerService(serviceName, instanceId, metadata) {
    const key = `/services/${serviceName}/${instanceId}`;
    const lease = client.lease(10);
    await lease.put(key).value(JSON.stringify(metadata));
    lease.on('lost', async () => {
        console.log('租约过期,重新注册...');
        await registerService(serviceName, instanceId, metadata);
    });
}
1
2
3
4
5
6
7
8
9
10

注册的时候我们按照 /services/服务名/实例id 的格式来指定 key。

也就是一个微服务可以有多个实例。

设置了租约 10s,这个就是过期时间的意思,然后过期会自动删除。

我们可以监听 lost 事件,在过期后自动续租。

当不再续租的时候,就代表这个服务挂掉了。

然后是服务发现:

// 服务发现
async function discoverService(serviceName) {
    const instances = await client.getAll().prefix(`/services/${serviceName}`).strings();
    return Object.entries(instances).map(([key, value]) => JSON.parse(value));
}
1
2
3
4
5

服务发现就是查询 /services/服务名 下的所有实例,返回它的信息。

// 监听服务变更
async function watchService(serviceName, callback) {
    const watcher = await client.watch().prefix(`/services/${serviceName}`).create();
    watcher .on('put', async event => {
        console.log('新的服务节点添加:', event.key.toString());
        callback(await discoverService(serviceName));
    }).on('delete', async event => {
        console.log('服务节点删除:', event.key.toString());
        callback(await discoverService(serviceName));
    });
}
1
2
3
4
5
6
7
8
9
10
11

通过 watch 监听 /services/服务名下所有实例的变动,包括添加节点、删除节点等,返回现在的可用节点。

我们来测试下:

(async function main() {
    const serviceName = 'my_service';
    
    await registerService(serviceName, 'instance_1', { host: 'localhost', port:3000 });
    await registerService(serviceName, 'instance_2', { host: 'localhost', port:3002 });

    const instances = await discoverService(serviceName);
    console.log('所有服务节点:', instances);

    watchService(serviceName, updatedInstances => {
        console.log('服务节点有变动:', updatedInstances);
    });
})();
1
2
3
4
5
6
7
8
9
10
11
12
13

跑起来确实能获得服务的所有节点信息:

当在 etcdctl 里 del 一个服务节点的时候,这里也能收到通知:

etcdctl del /services/my_service/instance_2
1

这样,我们就实现了服务注册、服务发现功能。

有的同学可能问了:redis 不也是 key-value 存储的么?为什么不用 redis 做配置中心和注册中心?

因为 redis 没法监听不存在的 key 的变化,而 etcd 可以,而配置信息很多都是动态添加的。

当然,还有很多别的原因,毕竟 redis 只是为了缓存设计的,不是专门的配置中心、注册中心的中间件。

专业的事情还是交给专业的中间件来干。

案例代码在小册仓库 (opens new window)

也在这里贴一份:

const { Etcd3 } = require('etcd3');
const client = new Etcd3({
    hosts: 'http://localhost:2379',
    auth: {
        username: 'root',
        password: 'guang'
    }
});

// 保存配置
async function saveConfig(key, value) {
    await client.put(key).value(value);
}

// 读取配置
async function getConfig(key) {
    return await client.get(key).string();
}

// 删除配置
async function deleteConfig(key) {
    await client.delete().key(key);
}
   
// 服务注册
async function registerService(serviceName, instanceId, metadata) {
    const key = `/services/${serviceName}/${instanceId}`;
    const lease = client.lease(10);
    await lease.put(key).value(JSON.stringify(metadata));
    lease.on('lost', async () => {
        console.log('租约过期,重新注册...');
        await registerService(serviceName, instanceId, metadata);
    });
}

// 服务发现
async function discoverService(serviceName) {
    const instances = await client.getAll().prefix(`/services/${serviceName}`).strings();
    return Object.entries(instances).map(([key, value]) => JSON.parse(value));
}

// 监听服务变更
async function watchService(serviceName, callback) {
    const watcher = await client.watch().prefix(`/services/${serviceName}`).create();
    watcher.on('put', async event => {
        console.log('新的服务节点添加:', event.key.toString());
        callback(await discoverService(serviceName));
    }).on('delete', async event => {
        console.log('服务节点删除:', event.key.toString());
        callback(await discoverService(serviceName));
    });
}

// (async function main() {
//     await saveConfig('config-key', 'config-value');
//     const configValue = await getConfig('config-key');
//     console.log('Config value:', configValue);
// })();

(async function main() {
    const serviceName = 'my_service';
    
    await registerService(serviceName, 'instance_1', { host: 'localhost', port:3000 });
    await registerService(serviceName, 'instance_2', { host: 'localhost', port:3002 });

    const instances = await discoverService(serviceName);
    console.log('所有服务节点:', instances);

    watchService(serviceName, updatedInstances => {
        console.log('服务节点有变动:', updatedInstances);
    });
})();
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

# 总结

微服务架构的系统中少不了配置中心和注册中心。

不同服务的配置需要统一管理,并且在更新后通知所有的服务,所以需要配置中心。

微服务的节点可能动态的增加或者删除,依赖他的服务在调用之前需要知道有哪些实例可用,所以需要注册中心。

服务启动的时候注册到注册中心,并定时续租期,调用别的服务的时候,可以查一下有哪些服务实例可用,也就是服务注册、服务发现功能。

注册中心和配置中心可以用 etcd 来做,它就是一个专业做这件事的中间件,k8s 就是用的它来做的配置和服务注册中心。

我们用 docker 跑了 etcd server,它内置了命令行工具 etcdctl 可以用来和 server 交互。

常用的命令有 put、get、del、watch 等。

在 node 里可以通过 etcd3 这个包来操作 etcd server。

稍微封装一下就可以实现配置管理和服务注册、发现的功能。

在微服务架构的后端系统中,配置中心、注册中心是必不可少的组件,不管是 java、go 还是 Node.js。

编辑 (opens new window)
上次更新: 2025/10/27 10:53:52
Nest 的 Monorepo 和 Library
Nest 集成 Etcd 做注册中心、配置中心

← Nest 的 Monorepo 和 Library Nest 集成 Etcd 做注册中心、配置中心→

最近更新
01
UNiAPP中使用虚拟列表
01-09
02
uni统计使用
11-12
03
原生三方SDK集成探索
11-12
更多文章>
Copyright © 2019-2026 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式