nns

package module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 11, 2026 License: MIT Imports: 11 Imported by: 1

README

nns

NNS 服务治理门面——一行创建/mock 模式/实例缓存与降级

🌐 项目主页:https://oekit.github.io/ · 名称由来:Open Easy Kit(开源 · 易用 · 工具集合) 🤖 AI Agent:本仓规范与硬规则见 AGENTS.md;机器可读索引见 llms.txt(接口签名以源码为准)。

安装

go get github.com/oekit/nns@v0.6.0

这个仓库做什么

nns 是 NNS 服务治理门面:provider 无关的服务发现、配置订阅、实例缓存、多级降级。 nns.New 一行创建,mock 模式让本地开发零 Nacos 依赖。

快速使用

import (
    nns "github.com/oekit/nns"
    nacospkg "github.com/oekit/registry/nacos"
)

// 生产:默认对接 Nacos;本地:WithMockMode(true) 零外部依赖
c := nns.New(nacospkg.Bootstrap{ServiceName: "my-service"},
    nns.WithMockMode(true),
    nns.WithStaticInstance("dep-svc", []registry.Instance{{IP: "10.0.0.1", Port: 9000}}))

_ = c.Init(ctx, "my-service", []string{"dep-svc"})
insts, _ := c.GetInstances(ctx, "dep-svc")
defer c.Close() // 终结化:注销订阅 + 关闭 SDK 连接(幂等)

关键语义

  • Close 是终结操作:默认路径下 SDK gRPC 连接已关闭,复用需重新 New
  • ServiceClient() 是逃生口:暴露底层客户端给适配器与端点工厂
  • bootstrap 集成:RunWithLifecycle(app, "svc", port, opts...) 自动注册/注销

多端点服务:一次连接注册多个名字

一个服务对外暴露多个端点(HTTP + RPC 双协议,分别注册在短名与完整 RPC 服务名下)时,用 RegisterAs 把每个名字各注册一次即可——同一个 SDK 连接, 不必为每个名字各开一个门面实例(那会翻倍 Nacos gRPC 长连接与后台 goroutine):

nnsc := nns.New(cfg)
// HTTP 端口注册在短名下
_ = nnsc.RegisterAs(ctx, "shop-http", 8080)
// RPC 端口注册在完整 RPC 服务名下
_ = nnsc.RegisterAs(ctx, "com.example.shop.order", 9090)
// 关停时对称注销(注册了几个名字就要注销几个名字)
_ = nnsc.DeregisterAs(ctx, "shop-http", 8080)
_ = nnsc.DeregisterAs(ctx, "com.example.shop.order", 9090)

RegisterAs 不要求先 Init(服务名由调用方显式给出);Register 仍走 Init 记录的自身服务名。门面还提供 Registrar() / Discoverer() 逃生口, 把「注册什么」交还给调用方,而 SDK 连接与生命周期仍由门面持有。

注册中心优先、静态地址兜底

静态实例默认是短路语义:配了就直接返回,注册中心不被查询(适合本地开发、 测试)。生产形态需要「注册中心优先、静态地址兜底」时,用 WithStaticFallback (单服务)或 WithFallbackOrder(全局):

// 单服务:静态地址只是最后一层兜底
c := nns.New(cfg,
    nns.WithStaticFallback("shop-dep", []registry.Instance{{IP: "10.0.0.1", Port: 9000}}),
)
// 全局:所有服务的静态实例都降级为兜底
c := nns.New(cfg,
    nns.WithFallbackOrder(client.FallbackRegistryFirst),
)

注册中心有健康实例时以注册中心为准(静态地址在实例换 IP 后不会跟着变); 注册中心冷启动、故障且缓存为空时才用静态地址兜底。缺省仍是静态优先, 既有调用方行为不变。

边界

矩阵总览(24 活跃模块,自动生成)

模块 内容 矩阵内依赖
pkg 微工具集 31 合 1(errors/bloomx/contextx/ctxscope/container/stringx/slicex/enumx/convert/encoding/jsonrepair/httpjson/mathx/pathx/pagination/ptrx/secretx/shellx/timex/cache/bufiox/strmap/singleflighttest/urlguard/webhook/cli/pack/procx/config/tarx/resumable;v0.10.0 起 uidgen/validator 拆至 kitx);errors(v0.12.0 起)New/As/Is/Unwrap 与 stdlib 同型透传——下游哨兵/As 判定单 import 即可,类型同一性红线可完整执行;cache(v0.4.0 起)含缓存接口 CacheAside + BatchGetter 批量能力(MGet)+ LocalCache/SingleFlightCache + cachetest 契约(Run 真实时间 + RunWithClock 假时钟确定性 TTL 边界)+ Checker;LocalCache 时钟可注入(LocalConfig.Now——TTL/NullTTL 全路径确定性测试,v0.13.0 起);v0.16.0 起 LocalCache 可选形状——NoBackgroundSweep 关闭后台清扫 goroutine(嵌入式/短命进程,公开 Sweep 手动清扫口)、CloneOnRead 读命中返回副本(调用方可改写不污染缓存)、EvictPolicy 可选 LRU(默认 FIFO 不变)、GetEx 区分未命中与值为 nil(新增能力以可选接口 Sweeper/HitGetter 外置,不破坏其它后端);v0.23.0 起补 Peek 非删除式读(可选接口 PeekGetter 外置——读到过期条目只算不命中、不删除它,stale-if-error 兜底需要那份过期真值留在表内;SingleFlightCache 包装 LocalCache 时曾因前置检查走 Get 惰性删除而静默废掉 StaleIfError,#72);container(v0.9.0 起,收编原 collectionx)泛型容器 Set/Ring/Queue/SafeMap + Map(sync.Map 背书)/Slice(并发安全);mathx/jsonrepair(v0.11.0)Unstable 单边向上抖动 Upward / 宽松解析 UnmarshalLenient / 抽对象与抽数组对称原语 ExtractObject·ExtractArray(v0.18.0 起——多段数组与散文含 '{' 的数组文本不再整段作废,取首个口径与对象面一致);timex/stringx(v0.7.0 起)时间段边界 BeginningOf/EndOf 七对(分/时/日/周/月/季/年,周起始显式参数)+ trie 多模式替换器 Replacer(最长匹配)/ NewMask 敏感词遮罩;stringx Masker(v0.16.0 起)正则脱敏——按形态分类打码(形态标签进掩码供审计读)+ 保留首尾各 ≤4 字符定位线索且不可复原(保留位封顶不随长度变化),规则表由调用方完全提供;singleflighttest(v0.8.0 起)singleflight 共享契约测试套件(internal 与 concurrency 双侧引用);urlguard(v0.15.0 起)出站 SSRF 防护原语——CheckURL 字面量校验(私网/loopback/链路本地含云元数据 169.254.169.254/CGNAT/ULA/组播保留、.internal·.local·localhost 域名、scheme 白名单、IPv4-mapped IPv6 归一)+ DialFunc 拨号层预解析校验与实际 RemoteAddr 端点核对闭合 TOCTOU(Happy-Eyeballs 覆盖),构造态豁免 WithAllowPrivate/WithAllowCIDRs/WithAllowDomains,与 httpx WithDialFunc 一行组合(httpx#11);cli(v0.16.0 起)框架无关可单测的子命令分发原语——App/Command + 退出码 0/1/2 写死在包里、usage 按 Summary 自动生成、不读 os.Args 不调 os.Exit(输出流全由调用方给);pack(v0.16.0 起)迁移包容器原语——zip + manifest(格式版本/条数/每文件 SHA-256)+ JSONL 流式读写 + Inspect 体检一次列全问题并区分包损坏与版本不认识;config/tarx(v0.42.0 起)启动期静态配置加载管线(yaml+env 覆盖+必填汇总校验+敏感字段脱敏 dump,#329)+ 目录 tar 传输三件套(同口径预扫 Scan/流式写出 Write/防御式解包 Extract 跳过并点名 Zip Slip 穿越条目 + sha256 manifest 自证,#326);v0.43.0 起三处收口——cache.Checker 构造期 fail-fast(nil refresh panic/WithClock(nil) 回退默认,#356)+ 刷新跑在 WithoutCancel 上(leader 断开不再把取消缓存成 failTTL 传染等待者,瞬态 Canceled 不入缓存,#352)+ SingleFlightCache 按调用注入路径不再按 key 合并(source 不可比较,合并即串味/丢 TTL/强刷退化;Refresh 与 load 分键,#353);container.Ring 逻辑序归基(32 位平台高频写入不再回绕出负下标 panic,#354);cache/pack/urlguard 三个裸 Option 改名 CheckerOption/WriterOption/GuardOption(#355,禁裸 Option 仓规收尾);v0.44.0 起三面补齐——shellx 词法事实面 Analyze(tokenize:普通词/重定向运算符+目标/动态词三类事实分离 + HasRedirect/HasDynamic/按控制符分段,前缀规则判据不再被「git status > /etc/passwd」整类绕过,判不了就报错不尽力解析,#358)+ procx 子进程内存上限原语(WithMemoryLimit/MemGroup:cgroup v2 memory.max 创建·设置·清理 + MemoryLimitCapable 问询面,兑现失败 fail closed 拒绝执行不静默不限、OOM-kill 与非零退出可辨、换出面一并封死、非 Linux 如实降级,#359)+ resumable 分片上传/断点续传会话面(新包:会话创建/分片登记/断点查询/完成/中止/Sweep 状态机,CAS 进度·合并抢占·双源对账·孤儿清扫四坑单点化,纯内存状态面 + ChunkStore 接口化存储后端,#361) —
concurrency 并发与流控原语(async/gopool/group/singleflight/progress/syncx/ratelimit/retry)+ window 滑动窗口 / breaker 自适应熔断(规范哨兵 ErrCircuitOpen)/ shedding CPU 自适应降载 / batch 攒批执行器(满条数/满字节/窗口去重)/ timewheel 单级时间轮;v0.5.0 起 retry 指数退避+抖动(MaxDelay 封顶/溢出饱和)、loop 周期原语(忙时丢 tick/单轮 panic 收编/FirstDelay 可打断)、async labeled RunSafe(panic 按任务标签上报)、syncx.FanInErr 逐槽错误(panic 槽显式标记)、KeyedLimiter 增强(容量护栏/RetryAfter/速率热更)并接入 pkg/singleflighttest 契约测试;v0.8.0 起 singleflight DoShared 折叠可观测(Result.Waiters)、async panic 统一收编 typed *PanicError(errors.As 可辨、携带栈)+ 实例级 Runner panic 路由、retry DelayHint(服务端 Retry-After 建议优先)+ JitterRatio 比例抖动 + retry/httpx Retryable 可重试性判定;v0.9.0 起 breaker 显式三态状态机 closed/open/half-open——冷却(WithProbeTimeout 缺省 1m)期满首个放行即半开探测、探测成功关闸清窗/失败重开、失联超时自动回收 + AbandonGen 显式放弃,GenBreaker 代绑定(AllowGen/SuccessGen/FailureGen/AbandonGen:代仅在探测放行自增、陈旧代回报整体作废、开放期失败不后移冷却);v0.10.0 起 WithCooldown 独立业务冷却期(缺省回落 probeTimeout)+ WithConsecutiveFailures 连续失败计数触发(与速率型并联,补低 QPS 信号,对齐 Envoy consecutive_5xx);v0.11.0 起 ratelimit Lockout 防爆破锁(fail2ban 形态:滑窗失败计数→阈值锁定拒绝 LockDuration→成功 Reset 清零/到期自解,CheckLocked 返回剩余时长);v0.12.0 起 retry/httpx RetryableWithHeaders(403 + Retry-After/X-RateLimit-Remaining 头感知可重试判定,HeaderSource 零依赖抽象)、syncx FanInLimit 受限并发扇出(固定 worker 防下游过载)+ FanInLimitCtx(v0.14.0,ctx 感知:ctx 已取消未启动项 fn 一次不调、剩余槽快速排空 Canceled、在飞项照常落槽)、ratelimit WithTokenClock 时钟注入;v0.13.0 起 breaker 熔断期收敛到探测级(forcePass 强制探活 + 冷却期满半开探测,其余拒绝——SRE 公式退役为 closed→open 触发器,WithConsecutiveFailures 触发后流量真停)+ State()/State 枚举可观测出口;v0.16.0 起 ratelimit NewKeyedLimiterByFunc[K]——factory func(K) Limiter 按 key 定配额(一个管理器按接口维度分档位,NewKeyedLimiterOf 零破坏随迁;未登记 key 拿 factory 兜底档的坑已在 doc 点名,#31);v0.17.0 起 KeyedLimiter.Create 护栏内原子登记(表满先回收空闲与 Allow 同路、仍满拒绝计 OverCapHits、已存在不覆盖——热更走 Replace;解决 ByFunc factory 库锁内调用致消费方锁序倒置,#33);v0.18.0 起 retry.DoValue 泛型伴生——带返回值的重试(DoValue[T](ctx, cfg, fn func(attempt int) (T, error));Do 改为委托给 DoValue——退避序列/抖动/MaxDelay 封顶/DelayHint 优先/Retryable 门控/ctx 取消只有一份实现,杜绝「同一个仓里两份实现」;失败返回零值与最后一次错误) pkg
logx 统一日志门面(多后端可替换 + caller 归属;Sink 已迁嵌套模块 logx/sink,根包纯 stdlib);WithFilter 脱敏/丢弃包装(FilterKey 命中键替换 ***/FilterFunc 丢整条,子 logger 保持规则)、Slogger/SlogHandler 正向桥(三方 slog 直接消费 logx 生态)、TraceHandler 任意 slog.Handler 自动注入 trace_id、NewAuditLogger 审计留痕(级别锁 Info,不受 LOG_LEVEL 降级)、LevelEnabler 热路径提前判级、ParseLogLevel 返回 (Level, error) + ParseLogLevelOr 配错退默认(v0.7.0);优雅关停正身在 core/lifecycle.Manager(logx/shutdown 仅余 Deprecated 别名);桥接子包 hlog/hertzlog/kitexlog 为嵌套模块按需引入 core
core 应用基建件——governance 治理 schema、config 配置加载(LoadBootstrap/LoadDotEnv/Get* 系、default/env/range/options tag 校验、OverlaySource 多源叠加热更、ApplyEnvOverlay env 优先 12-factor 形态)、transport 协议无关传输上下文(Transporter/Header/Server·Client ctx 存取,仅标准库)、chain 中间件链(Handler/Middleware/Chain/Recovery/Timeout,与 nns-endpoint.Endpoint 同形)、lifecycle 契约 + Manager 优雅关停(自 logx/shutdown 迁入,日志 *slog.Logger——core 不再依赖 logx)、network IP 工具、version;v0.9.0 起 config FieldSet 显式提供集合(yaml 显式 port:0 不再被 default/env 覆盖——LoadBootstrap 默认接线,免全量指针化)+ require tag(必须 yaml/env 提供,缺失列全并附 env 提示)+ v0.9.1 修 yaml:,inline 字段在 FieldSet 下漏标记(inline 叶子显式零值曾被 default 覆盖);v0.9.2 env 卫生改纯静态(仅格式/重名,移除依赖进程环境的拼写冒烟检查——误报 PATH/HOME、漏无人设置的真拼错,core#8),命名一致性走 LintEnvNaming 诊断 / WithEnvNamingLint 可选 fail-closed / ignorederived="true" 豁免;v0.10.0 起 config 严格模式——LoadBootstrap[T](envFlag string, opts ...LoadOption) 传 config.WithStrict() 开启:未知键拒载(不再静默忽略),错误带嵌套路径 + 该层合法键清单 + 近似拼写建议(UnknownKeyError);默认行为不变,严格模式显式开启;v0.11.0 起 config 加载入口按来源拆开——LoadFile(任意路径:CLI -config 路径、容器内固定挂载位)+ Unmarshal(字节源:API 下发 / 模板渲染 / 测试夹具),与 LoadBootstrap(etc/ 约定)共享同一条解码线,因此严格模式诊断面(嵌套路径 / 合法键清单 / 拼写建议)与来源无关;LoadBootstrap 行为与错误文案逐字不变 —
kitx 标识与校验——uidgen(Snowflake 全局单例+Extract 元数据、手写 RFC 4122 v4 UUID 零第三方依赖、RandomHex/RandomString 拒绝采样随机串)+ validator(go-playground/validator v10 接口化门面:struct tag 校验/自定义规则注册/ValidationError Unwrap 为 BizError 400 对接 middleware.Fail)+ hash(v0.2.0 起非密码学哈希助手 Md5Hex/Md5Bytes——缓存键/幂等键/短唯一 code,格式稳定承诺,pkg 2026-09-15 治理移除弱哈希后 kitx 侧收编 pkg#10),自 pkg v0.10.0 拆出 logx、pkg
metrics Prometheus 门面 + 弹性指标族 core
tracing OpenTelemetry 追踪 + trace_id 真相源(v0.3.0 起 OTLP 双协议导出:gRPC 缺省 / HTTP,Config.Protocol + Headers,切换协议不丢传播器/指标/旧 provider 关闭) logx
registry 注册发现核心(provider 无关 SPI:Registrar/Discoverer/Subscriber/ConfigStore + 实例模型 + netutil)+ sdbridge + registrymocks——根仓零第三方依赖;Nacos 实现与 Nacos 配置源已拆成嵌套模块(v0.5.0 起:registry/nacosimpl、registry/nacos——nacos-sdk-go 重依赖隔离在此,实测单独引入即拖 63 个第三方模块,不隔离时只要 SPI 接口的消费方也会被连带拖入) core
data 数据访问门面——复刻 ekit data/ 能力面:cache/rediscache/crud/distlock/gormx/redisx/timertask/cachetest 八子包 re-export(类型同一,实现住 pkg 与各子仓)+ cached 缓存读写编排(写后失效/事务提交后失效/查询缓存版本号失效/GetByIDs 批量点读 MGet 快路径/bloom 防穿透)+ twolevel 两级缓存与跨实例失效广播(v0.7.0 起本仓唯二非 re-export 实现);v0.15.0 起壳跟随源仓演进——cache 壳补 LocalCache 可选形状(EvictPolicy/Sweeper/HitGetter)、cachetest 壳补 5 个契约套件入口(套件 8 → 13 场景)、v0.18.0 起 cache 壳补 PeekGetter 别名 + cachetest 壳补 2 个 stale-if-error 契约入口(套件 13 → 15 场景)、timertask 壳的 Redis 形态随源仓 v0.2.0 迁至嵌套模块 timertask/redis(壳不再转发 RedisBackend/ShardManager,Redis 形态直接依赖该嵌套模块);all/ 聚合模块已废弃 retract concurrency、gormx、logx、pkg、redisx、timertask
gormx SQL/GORM 全家——连接池增强、框架无关 CRUD Service、仓储(原 oekit/gorm,v0.3.0 更名) logx、pkg
redisx Redis 全家——客户端封装(根包)、分布式缓存 cache/、分布式锁 distlock(Locker 同形可互换;Renew 手动续期(false=锁丢失,不进 Locker 接口保 etcdx 互换)+ WithAutoRenew 覆盖 TryLock 自动续期(v0.8.0 feat!:此前组合不续期);Semaphore ZSET 槽位信号量(v0.8.0:Acquire 同 identifier 幂等刷新 + Lua 内原子清过期槽崩溃自愈、Renew 仅未过期槽可续、Cleanup 返条数、无 Redis 降级 fail-fast))、分布式限流 ratelimit(Lua 三态 + 故障本地兜底);v0.9.0 起 distlock Lockout 分布式防爆破锁(Redis 侧 fail2ban:滑窗失败计数→阈值锁定 Penalty→Reset 清零,与 concurrency/ratelimit.Lockout 进程内孪生——多实例共享计数是存在理由;惩罚只按首次触发计算、nil client fail-fast 不降级,#6) concurrency、logx、pkg
etcdx etcd 全家——客户端封装(根包:生产默认值/Health 探活/Close(ctx) 幂等)、registry 注册发现实现(SPI 对接 registry/nacos 链路:Lease KeepAlive 注册 + 丢约指数退避自动重注册、prefix Watch 订阅推送、ConfigStore KV 配置监听)、distlock 分布式锁(Session+concurrency.Mutex 公平排队,接口与 redisx/distlock.Locker 同形可互换,可选无 etcd 降级放行) concurrency、logx、registry
httpx HTTP 客户端 + 负载均衡——服务发现/熔断(规范哨兵 ErrCircuitOpen)/重试/指标/OTel 透传;loadbalance Selector 形态(RoundRobin/Random/Weighted/一致性哈希权重感知虚拟副本/p2c 自适应——EWMA 经 DoneFunc 回写建立、Err 计 1s 惩罚;WithFilter 按元数据过滤;v0.4.0 起 Balancer 全家族删除无兼容路径);codec 注册制编解码器(json 内置 + form 表单,DoCodec 请求按 codec 序列化、响应按 Content-Type 反查回退请求 codec);v0.6.0 起 net/http 桥接层 NewRoundTripper/StdClient——存量标准库外呼一行 Transport 接入熔断/重试/指标/链路(重试缺省关显式开、非 2xx 非错误、hop-by-hop 隔离,#8);v0.8.0 起 SafeDialFunc/WithSafeDial 出站 SSRF 防护胶水(判定单源 pkg/urlguard v0.15.0:解析 IP 全量预检 + 实际 RemoteAddr 端点核对闭合重解析 TOCTOU、Happy-Eyeballs 覆盖,构造态豁免;v0.7.0 内置的独立 httpx/urlguard 实现收敛删除,#11) concurrency、logx、metrics、pkg、registry、tracing
network 轻量网络客户端——WebSocket/NATS/MQ 抽象、连接与流工具 concurrency、logx、tracing
timertask 分片定时任务(FNV-1a、心跳续约);v0.1.5 起可选可靠投递 DeliveryAtLeastOnce(租约 + Ack + 可见性超时;过期租约回扫重投/死信、停机 best-effort 收租,v0.1.7 接入生命周期真正生效);v0.2.0 起 Redis 后端迁至嵌套模块 timertask/redis(根包不再引 go-redis/logx/concurrency,require 仅 pkg——import timertask 与 import timertask/local 的模块依赖面均无 redis,纯 stdlib 嵌入式场景可用)+ 新增 local 子包 cron 调度器 Scheduler(表达式复用 pkg/timex/cron 不另写一份;并发度/CatchUp 补跑/退出语义明确,FakeClock 假时钟驱动、触发点序列可确定性断言) pkg
middleware Hertz 标准中间件栈(模块根 mw,双层目录已收平)+ contrib 桥接 + hmw + SheddingMiddleware 自适应降载 + chainmw 子包(transport 感知框架无关中间件,基于 core/chain+transport:Recovery 带 panic 隔离上报钩子 WithRecoveryReporter、AccessLog skip+redact+自定义字段、MetadataPropagate——kitex 经 kitex/chainmw.ToKitex 复用同一实现,根包 Recovery/AccessLog 已薄壳化复用 chainmw 内核、下游 handler 可经 transport.FromServerContext 读协议无关传输信息);v0.6.0 起 accesslog 凭证路径脱敏(WithAccessLogRedactPathPrefixes)、ratelimit 默认 KeyFunc 去端口(连接桶→地址桶);v0.11.0 起 WithAccessLogSink 落库 sink(AccessRecord{Method,Path,Status,Latency,ClientIP,UserAgent,TraceID} 与日志同源、c 同传可读业务键、error/panic 只记 Warn 不影响主链路、SkipPaths 命中不触发)+ chainmw.WithSink 内核同构(kitex 随 ToKitex 获得)+ StackConfig.DisableResponseWrap 响应自管一键形态(TraceID→Recovery→AccessLog)与 AccessLogOptions 透传 + doc/README「适用边界」段(响应契约统一 vs handler 自管的推荐组合,#18);v0.12.0 起 AccessLog panic 恢复点产出记录(AccessRecord.Panic 标记 + Err 摘要,Hertz status 按 Recovery 缺省 500)后按原值重抛——panic 请求不再零访问记录,sink/kitex 桥接同步留痕,#20;v0.13.0 起 accesslog 内建日志可关闭/改道(WithAccessLogSilent 只采不写、WithAccessLogLogger 注入宿主 logger 避免双写;静默下 skip/redact/sink/panic 记录照常),内核 chainmw.WithSilent 同构(kitex 随 ToKitex 获得),#38 concurrency、core、logx、metrics、pkg、registry、tracing
k8s Kubernetes 客户端封装——RESTConfig/Clientset/DynamicClient 构建(显式 kubeconfig → KUBECONFIG 显式意图 → in-cluster 自动 → ~/.kube/config 逐级回退,QPS/Burst 限流默认 50/100、负值=禁用客户端限流)+ leaderelection 子包 Lease 选主简化封装(OnStartedLeading/OnStoppedLeading/OnNewLeader 回调,主备单活,仅要求 Lease CRUD 权限;失权与 ctx 取消并存时失权优先上抛 ErrLostLeadership,续约失败与取消同窗不掩蔽) logx
nns NNS 服务治理客户端——provider 无关发现/配置/实例缓存 concurrency、core、logx、registry
nns-endpoint NNS 端点工厂——per-service 熔断、换实例重试、gRPC 池 httpx、logx、metrics、nns、registry
bootstrap 应用生命周期引导——New(WithServiceName/WithShutdownTimeout/WithBeforeServe) + Run(fn) + healthz;App.Ready/ReadyChan/IsReady 就绪门(幂等并发安全)——嵌套 bootstrap/nacos 自 v0.3.0 起 NNS 注册延迟到 Ready 之后(server 就绪 → userFn 触发 Ready → 才进注册中心)、关停序 nns 反注册先于 server 关闭;就绪门漏接时可观测——nacos 看门狗超时打 ERROR 点明「服务不会注册到注册中心」(WithReadyTimeout 可调)+ NNSComponent.RegistrationState() 暴露注册阶段(pending/registered/failed/skipped/shutdown);Metrics/Tracing 在嵌套模块 bootstrap/obs concurrency、core、logx
hertz Hertz 运行时集成(模块根扁平化)——日志桥接、生命周期、SSE/health/metrics、适配器、OTel、crudh/validatorh(基于 kitx/validator)/versionh(原 handler 微库收编);RunServer 变参就绪门 WithOnReady(全部 HostPort TCP 探活可建连后回调,探活超时按启动失败优雅关机;缺省超时 5s 经 WithReadyProbeTimeout 调整——探活目标是 server 自动取到的全部 HostPort,无探活地址覆盖选项(kitex 侧才有 WithReadyAddr)),配合 bootstrap Ready 实现 NNS 延迟注册;transportx(hertz→core/transport 同构适配器)+ chainmw 桥接 bootstrap、concurrency、core、gormx、kitx、logx、metrics、middleware、nns、pkg、registry、tracing
kitex Kitex 生态——日志桥接、治理热更、生命周期、BizError 桥接、kitexotel、istio(xDS 动态熔断,原 istio 微库收编);RunServer 变参就绪门(WithOnReady/WithReadyAddr/WithReadyProbeTimeout)+ chainmw.ToKitex(core/chain.Middleware → kitex 中间件,与 hertz 共享 middleware/chainmw 实现)+ transportx bootstrap、concurrency、core、logx、metrics、nns、pkg、registry
workflow 组合式流程编排——串行/并行/异步/条件分支 + Hook AOP concurrency、logx
kitex-examples Kitex echo 示例——client/server + IDL + 治理配置 bootstrap、core、kitex、logx、tracing

依赖严格自底向上;微库已合并为 pkg,旧微库仓已删除。

本仓在矩阵中的位置与依赖见上表「矩阵内依赖」列。历史沿革:源自 ekit 单体库 (git.enjoye.top/enjoydream/ekit v0.34.0,冻结维护),2026-10 拆分为独立 module。

License

MIT — 见 LICENSE

Documentation

Overview

Package nns 提供 Nacos Naming Service 高级封装,是 ekit 服务治理的官方推荐入口。

基于 registry 接口构建,提供 mock、静态实例、缓存、singleflight、多级降级等治理能力。 新服务应通过 NNSClient 接入,而非直接使用 registry/* 薄适配。

核心特性

  • 构造注入:每次 New 返回独立实例,推荐通过构造注入传递,不依赖全局状态
  • 服务初始化:自动加载本地和依赖服务配置
  • HTTP/gRPC Endpoint:自动生成服务端点
  • 服务注册/注销:与 Nacos Naming 集成;RegisterAs 支持同一连接注册多个服务名
  • 实例订阅:实时监听服务实例变更
  • 多级降级:静态实例 > 缓存 > 注册中心(降级序可配置,支持注册中心优先 + 静态兜底)
  • 框架适配:通过 ServiceClient() 暴露给 nns/kitex、nns/hertz 适配器

快速接入

创建 NNSClient(推荐通过构造注入传递):

ctx := context.Background()
nnsc := nns.New(cfg)
err := nnsc.Init(ctx, "my-service", []string{"user-service", "order-service"})
if err != nil {
    log.Fatal(err)
}
defer nnsc.Close()

注册服务:

err = nnsc.Register(ctx, port, registry.WithMetadata(version.Metadata()))

获取服务实例:

instances, err := nnsc.GetInstances(ctx, "user-service")

创建 HTTP Endpoint(nns-endpoint 模块,工厂由调用方持有;nns 包自拆分起 不再内置 MakeHTTPEndpoint——此前包注释仍展示已移除方法,照抄会编译失败, oekit/oekit.github.io#12):

import "github.com/oekit/nns-endpoint/endpoint"

factory := endpoint.NewHTTPEndpointFactory(nnsc.ServiceClient())
ep := factory("user-service")

框架适配:

// Kitex
cli, _ := echo.NewClient("dep",
    client.WithResolver(nnskitex.NewResolver(nnsc.ServiceClient())))
svr, _ := echo.NewServer(handler,
    server.WithRegistry(nnskitex.NewRegistry(nnsc.ServiceClient())))

// Hertz
cli.Use(sd.Discovery(nnshertz.NewResolver(nnsc.ServiceClient())))
svr := server.New(..., server.WithRegistry(nnshertz.NewRegistry(nnsc.ServiceClient())))

配置选项

  • WithCacheExpiry: 缓存过期时间,默认 30s
  • WithMockMode: 启用模拟模式(本地开发)
  • WithStaticInstance: 静态实例配置(短路语义)
  • WithStaticFallback: 静态兜底实例(注册中心优先,注册中心无果时参与)
  • WithFallbackOrder: 全局降级序(默认 client.FallbackStaticFirst)
  • WithLocalServiceConfig: 本地服务配置(不从 Nacos 获取)

多端点服务

一个服务对外暴露多个端点(HTTP + RPC 双协议)时,用 RegisterAs 把每个名字 各注册一次即可——同一个 SDK 连接,不必为每个名字各开一个门面实例:

err = nnsc.RegisterAs(ctx, "shop-http", 8080)
err = nnsc.RegisterAs(ctx, "com.example.shop.order", 9090)
// 关停时对称注销
err = nnsc.DeregisterAs(ctx, "shop-http", 8080)
err = nnsc.DeregisterAs(ctx, "com.example.shop.order", 9090)

与 bootstrap 集成

使用 bootstrap.Run 并设置 DepServices 自动初始化 NNS:

b, _ := bootstrap.New(bootstrap.WithServiceName("my-service"),
    bootstrapnacos.WithNNS(nacosCfg, port, []string{"dep.service"})) // bootstrap/nacos
b.Run(func(app *bootstrap.App) error {
    // ...
})

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type NNSClient

type NNSClient struct {
	// contains filtered or unexported fields
}

NNSClient NNS 服务客户端,封装了服务发现、负载均衡和端点管理功能。

门面是 provider 选择点:nns.New(cfg) 负责把 Nacos 配置构造为 registry/nacos 实现并注入 provider 无关的 ServiceClient(2026-09-19 重构:默认构造从 nns/client 上提至此,治理核心不再绑定 provider)。

func New

func New(cfg nacos.Bootstrap, opts ...NNSOption) *NNSClient

New 创建 NNS 客户端。 cfg 是 Nacos 连接配置,opts 可选配置 mock 模式、静态实例、缓存过期等。 返回的 NNSClient 需调用 Init 初始化后使用。

Provider 默认构造(非 mock 时):创建 naming/config SDK 客户端并注入 registry/nacos 的 Discoverer/Subscriber/Registrar/ConfigStore;构造失败 只 Warn 降级(缓存/静态实例路径),不阻断启动——与既有降级语义一致。 默认路径创建的 SDK 客户端归本实例所有,Close 时释放。

func (*NNSClient) AddStaticFallback added in v0.6.0

func (n *NNSClient) AddStaticFallback(serviceName string, instances ...registry.Instance)

AddStaticFallback 向指定服务追加**兜底**静态实例(与 WithStaticFallback 同语义)。

不改变发现路径(#128):注册中心仍是第一数据源,注册中心路径失败或返回 空时静态实例才参与取值。运行期热加备用地址用它。权重口径与 AddStaticInstance 一致。

func (*NNSClient) AddStaticInstance

func (n *NNSClient) AddStaticInstance(serviceName string, instances ...registry.Instance)

AddStaticInstance 向指定服务追加静态实例地址(**短路**语义)。

⚠️ 会改变发现路径(#128):缺省降级序下该服务随即短路注册中心。运行期 热加备用地址请用 AddStaticFallback(兜底语义)。切路径时打 Warn。 权重与 WithStaticInstance 同口径:Weight<=0 回填默认权重 10。

func (*NNSClient) Close

func (n *NNSClient) Close()

Close 关闭 NNSClient 持有的所有资源(ServiceClient 的配置/实例订阅、 回调状态、门面默认路径创建的 Nacos SDK 客户端)。 Close 是终结操作:默认路径下 SDK gRPC 连接已关闭,复用需重新 New。 端点工厂自 Round-4 起移至 ekit-nns-endpoint,其生命周期由构造方管理。

func (*NNSClient) CloseWithContext added in v0.5.1

func (n *NNSClient) CloseWithContext(ctx context.Context) error

CloseWithContext 关闭并返回错误(ctx 有界)。

与 Close 的差别只有两点:

  • **ctx 有界**:关闭流程要调 SDK 的注销订阅 / 停止配置监听,SDK 不响应时 Close 会**永久挂住**(调用方无从得知卡在哪一步)。这里把 ctx 透传给 调用方;Close 走默认 5s 上限。
  • **错误不吞**:底层关闭失败原样返回(Close 入口仍只记日志——它没有返回 值可给)。

幂等:与 Close 共享同一个 sync.Once,重复调用只有第一次真正执行。

func (*NNSClient) Deregister

func (n *NNSClient) Deregister(ctx context.Context, port int, opts ...registry.RegisterOptionFunc) error

Deregister 从注册中心注销当前服务实例。

func (*NNSClient) DeregisterAs added in v0.5.0

func (n *NNSClient) DeregisterAs(ctx context.Context, serviceName string, port int, opts ...registry.RegisterOptionFunc) error

DeregisterAs 从注册中心注销指定服务名下的当前服务实例。

与 RegisterAs 配套:注销的实例与服务名、端口必须与注册时一致,否则注册 中心匹配不到实例、注销会静默失效。

func (*NNSClient) Discoverer added in v0.5.0

func (n *NNSClient) Discoverer() registry.Discoverer

Discoverer 返回底层注入的发现器;未注入时返回 nil。

逃生口:与 Registrar 配套,让调用方直接驱动发现路径,连接与生命周期 仍归本门面。

func (*NNSClient) GetAllInstances

func (n *NNSClient) GetAllInstances(ctx context.Context, serviceName string) ([]registry.Instance, error)

GetAllInstances 获取指定服务的全部实例列表(包括不健康的实例)。

func (*NNSClient) GetInstances

func (n *NNSClient) GetInstances(ctx context.Context, serviceName string) ([]registry.Instance, error)

GetInstances 获取指定服务的健康实例列表。

func (*NNSClient) GetService

func (n *NNSClient) GetService(ctx context.Context, serviceName string) (*metadata.ServiceBasic, error)

GetService 获取指定服务的基本配置信息(实时拉取,返回真实错误)。

配置拉取失败(配置中心不可达)、配置 JSON 损坏、未注入 provider (ErrConfigStoreUnavailable)时返回错误,不再静默降级默认超时值(#77) ——调用方能区分「拉到的配置」与「兜底默认值」。

软降级语义(失败取默认值继续跑)的调用方:

  • 读内存缓存(Init/WatchService 维护、热更新):ServiceClient().GetServiceBasic(name);
  • 实时拉取但失败取默认值:ServiceClient().ServiceWithName(ctx, name)。

func (*NNSClient) Init

func (n *NNSClient) Init(ctx context.Context, serviceName string, depServices []string) error

Init 初始化本地服务并加载依赖服务配置,同时监听本地服务及所有依赖服务的配置变更(支持热更新)。

func (*NNSClient) Register

func (n *NNSClient) Register(ctx context.Context, port int, opts ...registry.RegisterOptionFunc) error

Register 向注册中心注册当前服务实例。

func (*NNSClient) RegisterAs added in v0.5.0

func (n *NNSClient) RegisterAs(ctx context.Context, serviceName string, port int, opts ...registry.RegisterOptionFunc) error

RegisterAs 向注册中心注册当前服务实例到**指定服务名**下。

与 Register 的区别只有一处:服务名由调用方显式给出,而不是取 Init 记录的 自身服务名。因此 RegisterAs 不要求先 Init。

一个服务对外暴露多个端点(HTTP + RPC 双协议、分别注册在短名与完整 RPC 服务名下)时,用同一个 SDK 连接把每个名字各注册一次即可——不必为每个名字 各开一个门面实例(那会翻倍 Nacos gRPC 长连接与后台 goroutine)。

端口各自传入(同一进程的两个端点端口不同)。IP 取 GetPodIP 探测的本机 IP。 opts 控制权重、元数据等注册选项。

配套的注销入口是 DeregisterAs:注册了几个名字就要注销几个名字,否则注册 中心里会留下无人回收的实例。

func (*NNSClient) Registrar added in v0.5.0

func (n *NNSClient) Registrar() registry.Registrar

Registrar 返回底层注入的注册器;未注入时返回 nil。

逃生口:把「注册什么」交还给调用方(如注册与本进程不同的实例地址), 而 SDK 连接与生命周期仍由本门面持有与释放。与 Discoverer 配套。

func (*NNSClient) ServiceClient

func (n *NNSClient) ServiceClient() *client.ServiceClient

ServiceClient 返回底层 *client.ServiceClient,供 nns/kitex、nns/hertz 等适配器使用。

推荐用法:

nnskitex.NewResolver(nnsc.ServiceClient())
nnshertz.NewRegistry(nnsc.ServiceClient())

不应绕过此访问器直接访问内部 ServiceClient 字段。

func (*NNSClient) SetStaticFallback added in v0.6.0

func (n *NNSClient) SetStaticFallback(serviceName string, instances []registry.Instance)

SetStaticFallback 设置指定服务的**兜底**静态实例(覆盖原有,与 WithStaticFallback 同语义)。不改变发现路径(#128)。

func (*NNSClient) SetStaticInstances

func (n *NNSClient) SetStaticInstances(serviceName string, instances []registry.Instance)

SetStaticInstances 设置指定服务的静态实例列表(覆盖原有,**短路**语义)。

⚠️ 同 AddStaticInstance:会改变发现路径(#128),热加备用地址请用 SetStaticFallback。权重与 WithStaticInstance 同口径:Weight<=0 回填默认 权重 10。

func (*NNSClient) Subscribe

func (n *NNSClient) Subscribe(ctx context.Context, serviceName string, callback func([]registry.Instance)) error

Subscribe 订阅指定服务的实例变更,当实例列表变化时回调 callback。

func (*NNSClient) Unsubscribe

func (n *NNSClient) Unsubscribe(ctx context.Context, serviceName string) error

Unsubscribe 取消订阅指定服务的实例变更。

type NNSOption

type NNSOption func(*clientOptions)

NNSOption NNSClient 的配置选项函数类型。

func WithCacheExpiry

func WithCacheExpiry(d time.Duration) NNSOption

WithCacheExpiry 设置实例缓存的过期时间。

func WithFallbackOrder added in v0.5.0

func WithFallbackOrder(order client.FallbackOrder) NNSOption

WithFallbackOrder 设置全局降级序:静态实例与注册中心谁优先。

缺省为 client.FallbackStaticFirst(静态实例短路注册中心,与既有行为 一致)。设为 client.FallbackRegistryFirst 后,全部服务的静态实例降级 为「兜底」:注册中心路径失败或返回空时才参与取值。

单服务可用 WithStaticFallback 单独指定兜底语义,不受全局序影响。

func WithLocalServiceConfig

func WithLocalServiceConfig(serviceName string, basic *metadata.ServiceBasic) NNSOption

WithLocalServiceConfig 配置本地服务配置(不从 Nacos 获取) 用于本地开发或测试环境,直接指定服务的超时、重试等配置

func WithMockMode

func WithMockMode(enabled bool) NNSOption

WithMockMode 设置是否启用 Mock 模式,Mock 模式下不连接 Nacos。

func WithStaticFallback added in v0.5.0

func WithStaticFallback(serviceName string, instances []registry.Instance) NNSOption

WithStaticFallback 配置指定服务的**兜底**静态实例。

与 WithStaticInstance 的区别只在参与时机:

  • WithStaticInstance:短路静态——配了就直接返回,注册中心不被查询;
  • WithStaticFallback:兜底静态——只有注册中心路径失败或返回空时才返回它。

典型用法是生产形态的「注册中心优先、静态地址兜底」:静态地址 (如 k8s Service DNS)与注册中心同时配置,注册中心有健康实例时以 注册中心为准(静态地址在实例换 IP 后不会跟着变),注册中心冷启动或 故障时才用静态地址兜底。

同一服务上重复配置以最后一次调用为准。

权重与 WithStaticInstance 同口径:Weight<=0 回填默认权重 10。

func WithStaticInstance

func WithStaticInstance(serviceName string, instances []registry.Instance) NNSOption

WithStaticInstance 配置指定服务的静态实例列表,用于本地开发或特殊场景。

默认是**短路**语义:配了该服务的静态实例后,GetInstances 直接返回它们, 不再查询注册中心。需要「注册中心优先、静态地址兜底」的生产形态请用 WithStaticFallback(或全局 WithFallbackOrder(FallbackRegistryFirst))。

权重与注册侧同口径:Weight<=0(漏配或显式 0)回填默认权重 10 (对齐 registry.Apply / registry.InfoFrom 的 defaultWeight),避免零权重 被下游加权均衡整只跳过变成「无可用实例」;显式正权重原样保留。

Directories

Path Synopsis
Package client 提供 NNS(enjoydream Naming Service)治理核心: 服务发现、配置管理、实例订阅与注册的 provider 无关实现。
Package client 提供 NNS(enjoydream Naming Service)治理核心: 服务发现、配置管理、实例订阅与注册的 provider 无关实现。
Package metadata 定义了 NNS(enjoydream Naming Service)中使用的核心元数据类型和常量。
Package metadata 定义了 NNS(enjoydream Naming Service)中使用的核心元数据类型和常量。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL