registry

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Mar 29, 2026 License: MIT Imports: 18 Imported by: 0

README

registry

Go Reference

registry 是 Genesis 的 L3 治理组件,提供基于 Etcd 的服务注册发现能力,并内置 gRPC resolver 集成。它解决的是服务实例动态上下线后的注册、发现和客户端负载均衡问题,而不是通用配置中心或多协议服务目录。

适用场景

  • 你的服务实例需要用 Etcd 做注册发现。
  • 你的客户端通过 gRPC 调用下游服务,希望直接拿到带 resolver 的 grpc.ClientConn
  • 你的进程只有一个 active 服务角色,接受“一个进程一个 active registry”的约束。

不适合的场景:

  • 同一个进程要同时维护多个独立 registry 实例。
  • 需要把 HTTP、HTTPS、gRPC 等多种协议地址混放在一个 endpoint 列表里。
  • 需要跨多种注册中心驱动统一切换。

核心能力

  • Register / Deregister:注册和注销服务实例,并用 Etcd lease 管理生命周期。
  • GetService / Watch:获取实例列表,或订阅实例变化。
  • GetConnection:返回已经接入 etcd resolver 的 gRPC 连接。
  • Close:停止后台 keepalive / watch,并尽力撤销 registry 创建的 lease。

关键边界

  • 进程内只允许一个 active registry。这是有意设计,不是实现限制。
  • ServiceInstance.Endpoints 只接受 gRPC 地址:
    • grpc://host:port
    • host:port
  • http://https:// 和其他协议地址不会通过注册校验。
  • GetConnection 只有在 ctx 带 deadline 时才会主动等待连接进入 Ready;否则只返回已经绑定 resolver 的 grpc.ClientConn
  • Watch 在遇到 Etcd compaction 时会回到最新快照,并基于快照与本地已知状态做 diff,补发必要的 PUT / DELETE 事件。
  • Close 会返回 lease 撤销失败,而不是只打日志。

快速开始

package main

import (
	"context"
	"time"

	"github.com/ceyewan/genesis/clog"
	"github.com/ceyewan/genesis/connector"
	"github.com/ceyewan/genesis/registry"
)

func main() {
	ctx := context.Background()

	logger, _ := clog.New(clog.NewDevDefaultConfig("registry-example"))
	defer logger.Close()

	etcdConn, _ := connector.NewEtcd(nil, connector.WithLogger(logger))
	defer etcdConn.Close()
	_ = etcdConn.Connect(ctx)

	reg, _ := registry.New(etcdConn, &registry.Config{
		Namespace:  "/genesis/services",
		DefaultTTL: 30 * time.Second,
	}, registry.WithLogger(logger))
	defer reg.Close()

	service := &registry.ServiceInstance{
		ID:        "user-service-001",
		Name:      "user-service",
		Version:   "v1.0.0",
		Endpoints: []string{"grpc://127.0.0.1:9001"},
	}

	_ = reg.Register(ctx, service, 30*time.Second)
}

服务注册

service := &registry.ServiceInstance{
	ID:        "order-service-001",
	Name:      "order-service",
	Version:   "v1.2.0",
	Endpoints: []string{"127.0.0.1:9002"},
	Metadata: map[string]string{
		"zone":   "ap-southeast-1a",
		"weight": "100",
	},
}

if err := reg.Register(ctx, service, 30*time.Second); err != nil {
	logger.Error("Register failed", clog.Error(err))
	return
}

说明:

  • ttl == 0 时使用 Config.DefaultTTL
  • ttl > 0 时必须至少为 1s
  • 注册成功后,registry 会在后台保持 lease keepalive。

服务发现

instances, err := reg.GetService(ctx, "order-service")
if err != nil {
	logger.Error("GetService failed", clog.Error(err))
	return
}

for _, inst := range instances {
	logger.Info("Service instance",
		clog.String("id", inst.ID),
		clog.Any("endpoints", inst.Endpoints))
}

监听服务变化

eventCh, err := reg.Watch(ctx, "order-service")
if err != nil {
	logger.Error("Watch failed", clog.Error(err))
	return
}

go func() {
	for event := range eventCh {
		switch event.Type {
		case registry.EventTypePut:
			logger.Info("Service updated", clog.String("id", event.Service.ID))
		case registry.EventTypeDelete:
			logger.Info("Service removed", clog.String("id", event.Service.ID))
		}
	}
}()

如果 watch 期间遇到 Etcd compaction,registry 不会直接把 revision 跳到最新值后继续监听,而是会读取当前快照并和本地已知实例做 diff,尽量把变化恢复成连续的 PUT / DELETE 事件。

gRPC 集成

推荐直接使用 GetConnection

import "google.golang.org/grpc/credentials/insecure"

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

conn, err := reg.GetConnection(ctx, "order-service",
	grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
	logger.Error("GetConnection failed", clog.Error(err))
	return
}
defer conn.Close()

说明:

  • service name 会被解析成 etcd:///order-service
  • 默认使用 gRPC 默认的 pick_first 负载均衡策略。
  • 如果 ctx 没有 deadline,GetConnection 不会主动等待连接进入 Ready

配置

字段 说明
Namespace Etcd key 前缀,默认 /genesis/services
DefaultTTL 默认租约时长,默认 30s,必须为 0>= 1s
RetryInterval watch / resolver 重试间隔,默认 1s

资源管理

registry 借用外部 EtcdConnector 的连接,不负责 connector 的生命周期。推荐遵循:

etcdConn, _ := connector.NewEtcd(...)
defer etcdConn.Close()

reg, _ := registry.New(etcdConn, cfg)
defer reg.Close()

Close() 会:

  • 停止 keepalive 和 watch 后台任务。
  • 尽力撤销当前 registry 创建的 lease。
  • 在 lease 撤销失败时返回错误。

常见误区

  • Endpoints 当成通用 URL 列表:当前只支持 gRPC 地址。
  • 以为 GetConnection 一定返回已经 ready 的连接:只有带 deadline 的 ctx 才会主动等待。
  • 忘记调用 Close():这样会让 keepalive 和 watch 在后台继续运行。

进一步阅读

Documentation

Overview

Package registry 提供了基于 Etcd 的服务注册发现组件,以及面向 gRPC 的 resolver 集成。

registry 位于 Genesis 的治理层,核心职责是把 Etcd 的租约、KV 和 watch 机制收敛成 一套更稳定的服务注册发现语义。它适合“一个进程对应一个 active 服务角色”的使用方式, 因此进程内只允许存在一个 active registry 实例。

这个组件当前有三个核心能力:

  • Register / Deregister:把服务实例注册到 Etcd,并用 lease 管理实例生命周期
  • GetService / Watch:获取实例列表,并订阅服务实例变化
  • GetConnection:把服务发现结果接入 gRPC resolver,返回可用于 RPC 的 ClientConn

registry 不负责 Etcd 连接的生命周期,它借用外部注入的 connector。调用方负责关闭 connector,也负责在 registry 不再使用时调用 Close。

ServiceInstance.Endpoints 在 registry 里不是通用 URL 列表,而是 gRPC 服务地址列表。 当前仅接受两种格式:

  • grpc://host:port
  • host:port

http://、https:// 或其他协议地址不会通过注册校验,也不会进入 resolver。

GetConnection 返回的是已经绑定 etcd resolver 的 gRPC 连接对象。如果调用方希望在 返回前主动等待连接 Ready,应传入带 deadline 的 context;如果传入没有 deadline 的 context,GetConnection 只保证 resolver 已配置,不保证连接已经 Ready。

Watch 会在 Etcd compaction 后回到最新快照,并基于快照与本地已知状态做 diff, 补发必要的 PUT / DELETE 事件,尽量维持事件流语义。

Close 会停止后台 watch / keepalive 任务,并尽力撤销当前 registry 创建的 lease。 如果 lease 撤销失败,Close 会把错误返回给调用方,而不是只写日志。

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrServiceNotFound 服务未找到
	ErrServiceNotFound = xerrors.New("service not found")

	// ErrServiceAlreadyRegistered 服务已注册
	ErrServiceAlreadyRegistered = xerrors.New("service already registered")

	// ErrInvalidServiceInstance 无效的服务实例
	ErrInvalidServiceInstance = xerrors.New("invalid service instance")

	// ErrRegistryAlreadyInitialized registry 已初始化
	ErrRegistryAlreadyInitialized = xerrors.New("registry already initialized")

	// ErrRegistryClosed registry 已关闭
	ErrRegistryClosed = xerrors.New("registry is closed")

	// ErrInvalidTTL 无效的 TTL
	ErrInvalidTTL = xerrors.New("invalid ttl")

	// ErrLeaseExpired 租约已过期
	ErrLeaseExpired = xerrors.New("lease expired")

	// ErrWatchClosed Watch 已关闭
	ErrWatchClosed = xerrors.New("watch closed")

	// ErrConnectionFailed 连接失败
	ErrConnectionFailed = xerrors.New("connection failed")
)

Functions

This section is empty.

Types

type Config

type Config struct {
	// Namespace Etcd Key 前缀,默认 "/genesis/services"
	Namespace string `yaml:"namespace" json:"namespace"`

	// DefaultTTL 默认服务注册租约时长,默认 30s
	DefaultTTL time.Duration `yaml:"default_ttl" json:"default_ttl"`

	// RetryInterval 重连/重试间隔,默认 1s
	RetryInterval time.Duration `yaml:"retry_interval" json:"retry_interval"`
}

Config Registry 组件配置

type EventType

type EventType string

EventType 表示服务事件类型。

const (
	EventTypePut    EventType = "PUT"    // 服务注册或更新
	EventTypeDelete EventType = "DELETE" // 服务注销
)

type Option

type Option func(*options)

Option 组件初始化选项函数

func WithLogger

func WithLogger(l clog.Logger) Option

WithLogger 注入日志记录器 组件内部会自动追加 "registry" namespace

type Registry

type Registry interface {

	// Register 注册服务实例。
	//
	// service.Endpoints 必须全部是 gRPC 地址,只接受 `grpc://host:port` 或 `host:port`。
	// ttl 为 0 时使用 Config.DefaultTTL;ttl 大于 0 时必须至少为 1 秒。
	Register(ctx context.Context, service *ServiceInstance, ttl time.Duration) error

	// Deregister 注销服务实例。
	Deregister(ctx context.Context, serviceID string) error

	// GetService 获取服务实例列表。
	GetService(ctx context.Context, serviceName string) ([]*ServiceInstance, error)

	// Watch 监听服务实例变化。
	//
	// 返回的通道会发送 PUT / DELETE 事件。发生 Etcd compaction 时,registry 会回到最新快照,
	// 基于快照与本地已知状态做 diff,并补发必要事件。
	Watch(ctx context.Context, serviceName string) (<-chan ServiceEvent, error)

	// GetConnection 获取指定服务的 gRPC 连接。
	//
	// 它内部封装了 resolver,并使用 gRPC 默认的 `pick_first` 负载均衡策略。只有当 ctx 带有 deadline 时,
	// 方法才会主动等待连接进入 Ready;否则仅返回已绑定 resolver 的 ClientConn。
	GetConnection(ctx context.Context, serviceName string, opts ...grpc.DialOption) (*grpc.ClientConn, error)

	// Close 停止后台任务并清理资源。
	//
	// Close 会停止 keepalive / watch,并尽力撤销当前 registry 创建的 lease。撤销失败会返回错误。
	Close() error
}

Registry 定义服务注册、服务发现和 gRPC 集成的核心能力。

func New

func New(conn connector.EtcdConnector, cfg *Config, opts ...Option) (Registry, error)

New 创建 Registry 实例(基于 Etcd) 这是标准的工厂函数,支持在不依赖 Container 的情况下独立实例化

参数:

  • conn: Etcd 连接器
  • cfg: Registry 配置
  • opts: 可选参数 (Logger)

使用示例:

etcdConn, _ := connector.NewEtcd(etcdConfig)
registry, _ := registry.New(etcdConn, &registry.Config{
    Namespace: "/genesis/services",
}, registry.WithLogger(logger))

type ServiceEvent

type ServiceEvent struct {
	Type    EventType        // 事件类型 (PUT/DELETE)
	Service *ServiceInstance // 服务实例信息
}

ServiceEvent 表示一次服务变化事件。

type ServiceInstance

type ServiceInstance struct {
	ID        string            `json:"id"`        // 唯一实例 ID (通常是 UUID)
	Name      string            `json:"name"`      // 服务名称 (如 user-service)
	Version   string            `json:"version"`   // 版本号
	Metadata  map[string]string `json:"metadata"`  // 元数据 (Region, Zone, Weight, Group 等)
	Endpoints []string          `json:"endpoints"` // 服务地址列表 (如 grpc://192.168.1.10:9090)
}

ServiceInstance 描述一个服务实例。

Endpoints 不是通用 URL 列表,而是 gRPC 地址列表,只接受 `grpc://host:port` 或 `host:port`。

Jump to

Keyboard shortcuts

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