119 lines
3.7 KiB
Go
119 lines
3.7 KiB
Go
// 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()
|
||
|
||
// 内存实现不主动清理过期 lease,EnsureOwner 会在写路径判断有效性。
|
||
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
|
||
}
|