nacos使用指南
这里写自定义目录标题
- nacos核心功能
- 配置管理
- 服务管理
- 服务健康检测
- nacos安装
- 核心依赖要求
- 单机模式
- 集群模式
- 代码示例(python)
- 配置变更监听
- 服务注册发现
- 集成flask实现获取参数以及动态更新
- 服务注册
- 服务发现
- 其他
- 临时实例
- 持久实例
- 保护阈值
- 常见原理问题
nacos核心功能
配置管理、服务管理、服务健康检测
配置管理
传统方案的弊端:
- 若多个服务依赖同一配置,修改时需逐一调整所有服务的配置文件,效率低下且易出错
- 传统方案中,配置修改后必须重启服务才能生效,对于高可用要求的业务,重启会导致服务中断,影响用户体验
- 开发、测试、生产环境的配置混杂在同一套文件中,易因 “环境串用” 引发线上故障
nacos配置管理主要功能:
- 配置统一管理
- 动态更新配置无需服务重启
- 配置的版本控制与回滚
- 命名空间隔离不通环境
服务管理
传统方案的痛点:
在微服务架构中,服务实例通常动态部署,IP 地址和端口会频繁变化,传统 “硬编码 IP” 的调用方式存在明显缺陷:
- IP 维护成本高:调用方需手动维护所有被调用方的 IP 列表,若服务实例扩缩容或 IP 变更,需同步修改调用方配置,极易遗漏;
- 负载均衡缺失:即使维护了 IP 列表,调用方也需自行实现负载均衡逻辑(如轮询、随机),否则会导致部分实例过载、部分实例闲置;
- 服务依赖不透明:无法清晰掌握 “哪些服务调用了当前服务”“当前服务依赖哪些服务”,故障排查时难以定位依赖链问题。
nacos服务管理:
- 自动化服务注册:服务实例启动时,自动向 Nacos 注册自身信息
- 智能化服务发现:调用方无需知道具体 IP,只需向 Nacos 查询 “服务名对应的可用实例列表”,Nacos 会返回健康状态正常的实例,并支持多种负载均衡策略
- 权重调节:通过调整实例权重(如给性能好的服务器设置更高权重),实现流量倾斜
- 标签路由:根据标签满足灰度发布、异地多活等场景
服务健康检测
传统方案的痛点:
- 故障发现滞后:传统方案中,服务调用方无法实时感知被调用方的健康状态,仍会向故障服务发送请求,导致大量 “无效调用”,引发超时、重试风暴;
- 故障扩散风险:若一个核心服务宕机但未被及时发现,调用方会持续重试,最终导致自身资源耗尽,引发 “雪崩效应”。
nacos健康检测:
服务实例启动后,定期向 Nacos 发送心跳包(默认 5 秒一次),若 Nacos 连续 3 次未收到心跳,判定服务 “不健康”,一旦检测到服务不健康,Nacos 会立即将其从 “可用服务列表” 中移除,避免调用方请求失败;同时支持通过短信、钉钉、Prometheus 等方式发送告警,提醒运维人员处理。
nacos安装
核心依赖要求
- Nacos 基于 Java 开发,需提前配置基础环境:
java环境:推荐 JDK 8+
数据库:内置 Derby 数据库,集群部署需 MySQL 5.7+ - 安装包获取
优先选择二进制包快速部署,下载地址:
官方 Releases 页面:https://github.com/alibaba/nacos
版本选择:生产环境推荐 LTS 版本(如 3.0.3),避免使用快照版;下载格式对应系统:
Linux/Unix:nacos-server-version.tar.gzWindows:nacos−server−{version}.tar.gz Windows:nacos-server-version.tar.gzWindows:nacos−server−{version}.zip
单机模式
解压下载文件,解压后目录结构:
nacos/
├─ bin/ # 启动/停止脚本
├─ conf/ # 配置文件(核心:application.properties、cluster.conf)
├─ logs/ # 运行日志(启动异常优先查此目录)
└─ target/ # 核心Jar包
可选配置调整
端口修改:默认占用 8848(珠峰高度寓意),若端口冲突,修改conf/application.properties:
server.port=9090 # 自定义端口
日志配置:通过conf/nacos-logback.xml调整日志级别、输出路径
2. 启动服务(分系统操作)
(1)Linux/Unix/Mac 系统
进入 bin 目录:cd /usr/local/nacos/bin
单机模式启动(必须加 - m standalone 参数,默认是集群模式):
linux:
sh startup.sh -m standalone
window
startup.cmd -m standalone
集群模式
集群模式需外部数据库(MySQL)存储数据,至少 3 个节点保证高可用,以下为核心步骤。
- 前置准备
新建数据库nacos_config(编码 UTF-8)
执行初始化脚本:conf/nacos-mysql.sql(脚本已内置在安装包,直接执行即可创建表结构)
修改所有节点的conf/application.properties,添加 MySQL 连接信息:
# 启用MySQL作为数据源
spring.datasource.platform=mysql
# 数据库实例数量(多主库可配置多个)
db.num=1
# 数据库连接地址(替换为实际IP和端口)
db.url.0=jdbc:mysql://192.168.1.100:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false
db.user.0=root
db.password.0=123456
在每个节点的conf/目录下创建cluster.conf,添加所有节点的 IP: 端口(禁止用localhost):
192.168.1.101:8848
192.168.1.102:8848
192.168.1.103:8848
2. 集群启动与验证
启动所有节点:每个节点执行启动命令(无需加 - m standalone,默认集群模式)
Linux:sh startup.sh
Windows:startup.cmd
这样就启动成功了,启动成功后默认用户名密码都是nacos
代码示例(python)
配置变更监听
from nacos import NacosClient
import time# 1. 初始化Nacos客户端(集群模式需传入多个地址,用逗号分隔)
nacos_client = NacosClient(server_addresses="192.168.1.100:8848", # Nacos服务地址(单机/集群)namespace="dev", # 命名空间ID(非名称,可在Nacos控制台“命名空间”页复制)username="nacos", # Nacos登录账号password="nacos123" # Nacos登录密码(生产环境需加密存储)
)# 2. 定义配置更新回调函数(配置变更时自动执行)
def config_update_callback(data_id, group, content):""":param data_id: 配置ID(如app-config.yml):param group: 配置分组(如flask-service):param content: 更新后的配置内容(字符串格式)"""print(f"\n=== 配置[{data_id}:{group}]已更新 ===")print(f"新配置内容:{content}")# 此处可添加业务逻辑(如更新全局配置变量、重启组件等)# 3. 拉取并监听配置(支持YAML/JSON/Properties格式)
def get_and_listen_config():# 3.1 主动拉取配置(首次加载)config_content = nacos_client.get_config(data_id="app-config.yml", # 配置IDgroup="flask-service" # 配置分组)print(f"首次拉取配置:\n{config_content}")# 3.2 监听配置变化(长期运行需保持客户端存活)nacos_client.add_config_watcher(data_id="app-config.yml",group="flask-service",callback=config_update_callback # 配置更新回调函数)# 模拟程序运行(实际项目中无需此循环,由业务逻辑保持进程存活)try:while True:time.sleep(30)except KeyboardInterrupt:print("程序退出,停止监听配置")if __name__ == "__main__":get_and_listen_config()
服务注册发现
from nacos import NacosClient
import socket# 1. 初始化Nacos客户端(同配置管理)
nacos_client = NacosClient(server_addresses="192.168.1.100:8848",namespace="dev",username="nacos",password="nacos123"
)# 2. 获取本地IP(用于服务注册,避免硬编码)
def get_local_ip():s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)try:s.connect(("8.8.8.8", 80))return s.getsockname()[0]finally:s.close()# 3. 注册服务到Nacos(含健康检查配置)
def register_service():local_ip = get_local_ip()service_name = "python-demo-service" # 服务名(需与调用方一致)service_port = 8080 # 服务端口# 注册服务实例nacos_client.register_instance(service_name=service_name,ip=local_ip,port=service_port,# 可选:添加元数据(如权重、服务版本)metadata={"weight": "1.0", "version": "v1.0"},# 健康检查配置(默认心跳检查,间隔5秒,超时15秒)health_check_type="heartbeat",heartbeat_interval=5000, # 心跳间隔(毫秒)ip_delete_timeout=15000 # 无心跳超时删除(毫秒))print(f"服务[{service_name}]已注册到Nacos:{local_ip}:{service_port}")# 4. 查询服务的健康实例列表(用于服务调用)
def get_healthy_instances():service_name = "order-service" # 目标服务名# 查询健康实例(仅返回状态为UP的实例)instances = nacos_client.list_instances(service_name=service_name,group_name="DEFAULT_GROUP", # 服务分组(默认DEFAULT_GROUP)healthy_only=True # 仅返回健康实例)if instances["hosts"]:print(f"\n服务[{service_name}]的健康实例:")for instance in instances["hosts"]:print(f"IP: {instance['ip']}, 端口: {instance['port']}, 权重: {instance['weight']}")return instances["hosts"]else:print(f"服务[{service_name}]无健康实例")return []if __name__ == "__main__":register_service()get_healthy_instances()# 保持服务运行(实际项目中由业务逻辑接管)while True:time.sleep(30)
集成flask实现获取参数以及动态更新
from flask import Flask, jsonify
from nacos import NacosClient
import socket# --------------------------
# 1. 全局配置(初始值,Nacos加载后会覆盖)
# --------------------------
GLOBAL_CONFIG = {"flask_port": 5000, # Flask服务端口"log_level": "INFO", # 日志级别"welcome_msg": "Hello!" # 自定义业务参数(如欢迎语)
}# --------------------------
# 2. Nacos核心交互(初始化、拉取配置、监听更新)
# --------------------------
def init_nacos():"""初始化Nacos客户端"""return NacosClient(server_addresses="192.168.1.100:8848", # 替换为你的Nacos地址namespace="dev", # 命名空间(默认public可删)username="nacos", # Nacos账号password="nacos123" # Nacos密码(生产环境需加密))def parse_properties_config(config_str):"""解析Nacos的Properties格式配置(无需PyYAML,原生字符串处理)"""config_dict = {}# 按行分割配置,跳过空行和注释(#开头)for line in config_str.strip().split("\n"):line = line.strip()if not line or line.startswith("#"):continue# 分割键值对(支持=号分隔,如 key=value)if "=" in line:key, value = line.split("=", 1) # 只分割第一个=(避免value含=)config_dict[key.strip()] = value.strip()return config_dictdef load_and_listen_nacos(nacos_client):"""拉取Nacos配置 + 监听配置更新"""# 拉取Nacos配置(DataID:flask-simple-config.properties,分组:DEFAULT_GROUP)try:# 从Nacos获取Properties格式配置nacos_config_str = nacos_client.get_config(data_id="flask-simple-config.properties",group="DEFAULT_GROUP")# 解析配置并更新全局变量nacos_config = parse_properties_config(nacos_config_str)if nacos_config:# 覆盖全局配置(仅更新Nacos中存在的键)for key, value in nacos_config.items():if key in GLOBAL_CONFIG:# 特殊处理:端口是数字,需转换类型if key == "flask_port":GLOBAL_CONFIG[key] = int(value)else:GLOBAL_CONFIG[key] = valueprint(f"✅ 从Nacos加载配置成功:{GLOBAL_CONFIG}")else:print("⚠️ Nacos配置为空,使用默认配置")except Exception as e:print(f"❌ 从Nacos加载配置失败,使用默认配置:{str(e)}")# 监听Nacos配置更新(配置变化时自动触发)def config_update_callback(data_id, group, content):print(f"\n🔄 Nacos配置[{data_id}:{group}]已更新")try:# 解析新配置并更新全局变量new_config = parse_properties_config(content)if new_config:for key, value in new_config.items():if key in GLOBAL_CONFIG:if key == "flask_port":GLOBAL_CONFIG[key] = int(value)else:GLOBAL_CONFIG[key] = valueprint(f"✅ 全局配置已更新:{GLOBAL_CONFIG}")except Exception as e:print(f"❌ 解析更新配置失败:{str(e)}")# 给Nacos配置添加监听器nacos_client.add_config_watcher(data_id="flask-simple-config.properties",group="DEFAULT_GROUP",callback=config_update_callback)def register_flask_service(nacos_client):"""(可选)将Flask服务注册到Nacos,便于其他服务发现"""try:# 获取本地IP(避免硬编码)local_ip = socket.gethostbyname(socket.gethostname())service_name = "flask-simple-service" # 服务名service_port = GLOBAL_CONFIG["flask_port"] # 从配置取端口# 注册服务到Nacosnacos_client.register_instance(service_name=service_name,ip=local_ip,port=service_port,metadata={"version": "v1.0", "type": "simple"} # 元数据(可选))print(f"✅ Flask服务[{service_name}]已注册到Nacos:{local_ip}:{service_port}")except Exception as e:print(f"❌ 注册服务到Nacos失败:{str(e)}")# --------------------------
# 3. Flask应用与接口
# --------------------------
def create_flask_app():"""创建Flask应用"""app = Flask(__name__)# 接口1:展示当前配置(验证Nacos配置是否生效)@app.route("/config", methods=["GET"])def show_config():return jsonify({"code": 200,"msg": "当前配置","data": GLOBAL_CONFIG # 直接返回全局配置})# 接口2:健康检查(Nacos可配置HTTP检查,可选)@app.route("/health", methods=["GET"])def health_check():return jsonify({"status": "UP","service": "flask-simple-service","log_level": GLOBAL_CONFIG["log_level"]})# 接口3:使用业务参数(验证动态更新效果)@app.route("/welcome", methods=["GET"])def welcome():return jsonify({"code": 200,"msg": GLOBAL_CONFIG["welcome_msg"], # 使用Nacos中的欢迎语配置"log_level": GLOBAL_CONFIG["log_level"] # 同时返回日志级别})return app# --------------------------
# 4. 程序启动入口
# --------------------------
if __name__ == "__main__":# 1. 初始化Nacosnacos_client = init_nacos()# 2. 拉取配置 + 监听更新load_and_listen_nacos(nacos_client)# 3. 注册服务到Nacos(可选,可注释)register_flask_service(nacos_client)# 4. 创建并启动Flask(端口从Nacos配置获取)app = create_flask_app()app.run(host="0.0.0.0", # 允许外部访问port=GLOBAL_CONFIG["flask_port"], # 端口来自Nacos配置debug=False # 生产环境禁用debug)
服务注册
from nacos import NacosClient
import socket
import time# --------------------------
# 1. 基础配置(需替换为你的Nacos信息)
# --------------------------
NACOS_ADDR = "192.168.1.100:8848" # Nacos服务地址(单机/集群用逗号分隔)
NACOS_NAMESPACE = "dev" # 命名空间(默认public可设为"")
NACOS_USER = "nacos" # Nacos登录账号
NACOS_PWD = "nacos123" # Nacos登录密码# 本服务信息(要注册到Nacos的服务)
SERVICE_NAME = "demo-user-service" # 服务名(消费者需用相同名称查询)
SERVICE_PORT = 9001 # 本服务端口(模拟实际服务端口)
SERVICE_METADATA = { # 服务元数据(可选,如版本、权重)"version": "v1.0","weight": "1.0","desc": "模拟用户查询服务"
}# --------------------------
# 2. 工具函数:获取本地IP(避免硬编码)
# --------------------------
def get_local_ip():"""自动获取本地有效IP(适配Windows/Linux)"""try:# 连接外部地址(无需成功,仅用于获取本地IP)s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.connect(("8.8.8.8", 80))local_ip = s.getsockname()[0]finally:s.close()return local_ip# --------------------------
# 3. 核心逻辑:初始化Nacos + 注册服务
# --------------------------
def register_service():# 3.1 初始化Nacos客户端nacos_client = NacosClient(server_addresses=NACOS_ADDR,namespace=NACOS_NAMESPACE,username=NACOS_USER,password=NACOS_PWD)# 3.2 获取本地IP和服务信息local_ip = get_local_ip()print(f"本地服务信息:IP={local_ip}, 端口={SERVICE_PORT}")# 3.3 注册服务到Nacos(Nacos自动维持心跳,默认5秒一次)try:nacos_client.register_instance(service_name=SERVICE_NAME, # 服务名(全局唯一)ip=local_ip, # 服务IPport=SERVICE_PORT, # 服务端口metadata=SERVICE_METADATA, # 元数据health_check_type="heartbeat" # 健康检查方式(心跳))print(f"✅ 服务[{SERVICE_NAME}]已注册到Nacos")except Exception as e:print(f"❌ 服务注册失败:{str(e)}")return# 3.4 维持服务运行(模拟实际服务持续提供功能,避免脚本退出)try:while True:print(f"⏳ 服务运行中...({time.strftime('%H:%M:%S')})")time.sleep(10) # 每10秒打印一次运行状态except KeyboardInterrupt:# 3.5 可选:脚本退出时注销服务(生产环境可省略,Nacos会自动清理超时服务)nacos_client.deregister_instance(service_name=SERVICE_NAME,ip=local_ip,port=SERVICE_PORT)print(f"\n✅ 服务[{SERVICE_NAME}]已从Nacos注销")# --------------------------
# 4. 启动服务提供者
# --------------------------
if __name__ == "__main__":register_service()
服务发现
from nacos import NacosClient
import time# --------------------------
# 1. 基础配置(与服务提供者一致)
# --------------------------
NACOS_ADDR = "192.168.1.100:8848"
NACOS_NAMESPACE = "dev"
NACOS_USER = "nacos"
NACOS_PWD = "nacos123"# 要查询的服务名(必须与服务提供者注册的名称一致)
TARGET_SERVICE_NAME = "demo-user-service"# --------------------------
# 2. 核心逻辑:初始化Nacos + 发现服务
# --------------------------
def discover_service():# 2.1 初始化Nacos客户端nacos_client = NacosClient(server_addresses=NACOS_ADDR,namespace=NACOS_NAMESPACE,username=NACOS_USER,password=NACOS_PWD)# 2.2 循环查询服务实例(模拟消费者定期更新服务列表)try:while True:print(f"\n--- {time.strftime('%H:%M:%S')} 开始查询服务 ---")try:# 从Nacos查询目标服务的【健康实例】(仅返回状态为UP的实例)instances = nacos_client.list_instances(service_name=TARGET_SERVICE_NAME,group_name="DEFAULT_GROUP", # 服务分组(默认DEFAULT_GROUP)healthy_only=True # 只取健康实例)# 2.3 解析查询结果if instances["hosts"]:print(f"✅ 找到[{len(instances['hosts'])}]个健康实例:")for idx, instance in enumerate(instances["hosts"], 1):print(f" 实例{idx}:IP={instance['ip']}, 端口={instance['port']}, 元数据={instance['metadata']}")# 实际场景:此处可根据实例IP+端口发起HTTP/GRPC调用(如requests.get(f"http://{ip}:{port}/api/user"))else:print(f"❌ 未找到[{TARGET_SERVICE_NAME}]的健康实例(可能服务未注册或已下线)")except Exception as e:print(f"❌ 查询服务失败:{str(e)}")# 每5秒查询一次(模拟消费者定期刷新服务列表)time.sleep(5)except KeyboardInterrupt:print("\n✅ 停止查询服务")# --------------------------
# 3. 启动服务消费者
# --------------------------
if __name__ == "__main__":discover_service()
当然有很多http框架nacos的使用都封装好了,不用这么麻烦
其他
用这个类比再看实际项目:
大促时临时扩容的服务 → 临时实例;
全年不关机的支付服务 → 持久实例;
所有核心服务都设 0.8 的保护阈值 → 防止服务雪崩。
临时实例
健康检查靠 “心跳”:临时实例启动后,会每隔几秒给 Nacos 发 “心跳包”(类似打卡);
下线自动删除:如果实例宕机 / 停止(比如服务器断电),没发心跳,Nacos 会自动把这个实例从 “服务列表” 里删掉;
不持久化存储:Nacos 不会把临时实例的信息存到数据库(重启 Nacos 后,临时实例信息会消失);
适合场景:服务需要 “弹性扩缩容”(比如大促时加机器,促后删机器),比如电商的 “订单服务”“商品服务”。
持久实例
健康检查靠 “主动探测”:Nacos 会主动去访问实例(比如发 HTTP 请求、测 TCP 连接),判断是否健康;
下线不删除,只标 “不健康”:实例宕机后,Nacos 不会删掉它,只会把状态改成 “不健康”(类似 “请假中”);
持久化存储:实例信息会存在 Nacos 的数据库里(就算 Nacos 重启,实例信息还在);
适合场景:服务需要 “长期稳定运行”,不会随便删节点,比如 “用户服务”“支付服务”(核心服务,节点固定)。
临时实例和持久实例,保护阈值
保护阈值
保护阈值是一个百分比(0~1),比如设为 0.8(80%),意思是:
当 “健康实例数 / 总实例数 ≤ 保护阈值” 时,Nacos 会暂时把不健康的实例也加入 “可用列表”,让调用方可以访问这些 “亚健康实例”—— 避免因健康实例太少,导致所有请求都失败(服务雪崩)。
常见原理问题
-
如何实现服务注册与发现?
服务注册:服务启动时,通过 Nacos SDK(如 Java/Python 的nacos-sdk)向 Nacos 服务端发送请求,携带自身的IP、端口、服务名、元数据(如版本、权重)等信息。Nacos 收到后,将该实例记录到对应服务的 “实例列表” 中。
服务发现:消费者服务同样通过 SDK,向 Nacos 查询目标服务的实例列表。Nacos 返回 “健康实例” 的信息(IP + 端口等),消费者基于这些信息发起服务调用(如 HTTP 请求、RPC 调用)。 -
健康检查的机制是什么?
Nacos 对实例的健康检查分两种模式(由实例 “持久 / 临时” 属性决定):
临时实例(默认):实例主动向 Nacos 发 “心跳包”(默认 5 秒一次)。若 Nacos 连续 15 秒没收到心跳,标记实例为 “不健康”;30 秒没收到,直接删除实例(临时实例不持久存储,下线即 “除名”)。
持久实例:Nacos主动向实例发起探测(如 HTTP GET 请求/health接口、TCP 连接探测)。若探测失败,标记实例为 “不健康”,但不会删除(持久实例信息存在数据库,需手动下线)。 -
自我保护的原理是什么?
自我保护的核心是 “保护阈值”(默认 0.8,可配置,范围 0~1)。当 健康实例数 / 总实例数 ≤ 保护阈值 时,Nacos 会触发 “自我保护模式”:
即使实例 “不健康”,也会暂时保留在 “可用实例列表” 中,允许消费者调用。
目的:防止 “健康实例过少时,所有请求涌向少量健康实例,导致它们被压垮,最终服务彻底不可用”(即 “服务雪崩”)。 -
事件监听是如何设计的?
Nacos 采用 “发布 - 订阅(Publish-Subscribe)” 模式实现事件监听:
客户端(服务或配置消费者)向 Nacos “订阅” 感兴趣的事件(如 “服务 A 的实例变化”“配置 dataId 为 xxx 的配置变化”)。
当 Nacos 检测到事件发生(如服务 A 新增实例、配置 xxx 被修改),会主动 “发布” 事件,推送通知给所有订阅的客户端。
客户端收到通知后,执行自定义逻辑(如更新本地缓存的实例列表、重载配置)。