2026-05-06 00:30:22 +08:00

119 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package router 维护 room-service 的房间 owner 路由目录和 lease 语义。
package router
import (
"context"
"sync"
"time"
"hyapp/pkg/idgen"
)
// Lease 表达房间当前执行权归属。
type Lease struct {
// RoomID 是租约所属房间。
RoomID string
// NodeID 是当前拥有房间执行权的 room-service 实例。
NodeID string
// LeaseToken 是租约实例标识,便于未来做续约和 fencing。
LeaseToken string
// ExpiresAt 是租约过期时间,过期后其他节点可以接管并恢复房间。
ExpiresAt time.Time
}
// ValidAt 判断租约在指定时刻是否仍有效。
func (l Lease) ValidAt(now time.Time) bool {
// 零值过期时间永远无效,避免空 lease 被误认为可继续持有。
return !l.ExpiresAt.IsZero() && now.Before(l.ExpiresAt)
}
// Directory 抽象 room_id -> node_id 的房间归属目录。
type Directory interface {
// Lookup 查询当前记录的房间 owner不会主动接管。
Lookup(ctx context.Context, roomID string) (Lease, bool, error)
// EnsureOwner 在无 owner、owner 过期或 owner 是当前节点时授予当前节点执行权。
EnsureOwner(ctx context.Context, roomID string, nodeID string, now time.Time, ttl time.Duration) (Lease, error)
// VerifyOwner 校验当前节点和 lease token 仍然持有执行权,不续租也不接管。
VerifyOwner(ctx context.Context, roomID string, nodeID string, leaseToken string, now time.Time) (bool, error)
// ForceExpire 只用于测试模拟租约过期。
ForceExpire(roomID string)
}
// MemoryDirectory 是首版测试与本地运行使用的内存目录实现。
type MemoryDirectory struct {
// mu 保护 leases测试里可能用多个服务实例模拟接管。
mu sync.Mutex
// leases 保存 room_id -> lease。
leases map[string]Lease
}
// NewMemoryDirectory 初始化空的内存目录。
func NewMemoryDirectory() *MemoryDirectory {
return &MemoryDirectory{
leases: make(map[string]Lease),
}
}
// Lookup 查询当前房间归属。
func (d *MemoryDirectory) Lookup(_ context.Context, roomID string) (Lease, bool, error) {
d.mu.Lock()
defer d.mu.Unlock()
// 内存实现不主动清理过期 leaseEnsureOwner 会在写路径判断有效性。
lease, ok := d.leases[roomID]
return lease, ok, nil
}
// EnsureOwner 在租约不存在、已过期或已归当前节点时,授予本节点执行权。
func (d *MemoryDirectory) EnsureOwner(_ context.Context, roomID string, nodeID string, now time.Time, ttl time.Duration) (Lease, error) {
d.mu.Lock()
defer d.mu.Unlock()
current, ok := d.leases[roomID]
if ok && current.ValidAt(now) && current.NodeID != nodeID {
// 其他节点持有有效 lease 时返回现有 owner调用方应拒绝执行房间命令。
return current, nil
}
// 没有 lease、lease 过期或当前节点续租时生成新 token 和过期时间。
next := Lease{
RoomID: roomID,
NodeID: nodeID,
LeaseToken: idgen.New("lease"),
ExpiresAt: now.Add(ttl),
}
d.leases[roomID] = next
return next, nil
}
// VerifyOwner 校验调用方手里的 lease token 仍是当前有效 owner。
func (d *MemoryDirectory) VerifyOwner(_ context.Context, roomID string, nodeID string, leaseToken string, now time.Time) (bool, error) {
d.mu.Lock()
defer d.mu.Unlock()
current, ok := d.leases[roomID]
if !ok {
return false, nil
}
return current.NodeID == nodeID && current.LeaseToken == leaseToken && current.ValidAt(now), nil
}
// ForceExpire 只用于测试时模拟租约自然过期。
func (d *MemoryDirectory) ForceExpire(roomID string) {
d.mu.Lock()
defer d.mu.Unlock()
lease, ok := d.leases[roomID]
if !ok {
// 没有租约时无需处理,测试可安全重复调用。
return
}
lease.ExpiresAt = time.Unix(0, 0)
d.leases[roomID] = lease
}