feat: 添加 Session 亲和路由支持(NewSession / WithAffinity)
- 新增 NewSession(pool):返回固定到同一 worker 进程的 Pool 视图 - 新增 WithAffinity(ctx, key):相同 key 通过 FNV hash 稳定路由到同一 worker - pool.acquire 支持亲和路由,无亲和时保持轮询 - example 添加 Session / WithAffinity / 全局变量共享三组演示
This commit is contained in:
@@ -34,6 +34,7 @@ func main() {
|
||||
|
||||
demoPool(ctx, pool)
|
||||
demoServer(ctx, script)
|
||||
demoSession(ctx, script)
|
||||
}
|
||||
|
||||
func demoPool(ctx context.Context, pool gobridge.Pool) {
|
||||
@@ -172,6 +173,86 @@ func (s *goService) MakeUser(ctx context.Context, uid int) (User, error) {
|
||||
return User{ID: uid, Name: fmt.Sprintf("user_%d", uid), Score: float64(uid) * 1.5}, nil
|
||||
}
|
||||
|
||||
func demoSession(ctx context.Context, script string) {
|
||||
fmt.Println("\n── Session 亲和示例(workers=2)─────────────────────────────────")
|
||||
|
||||
pool, err := gobridge.NewPool(script, gobridge.WithWorkers(2))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
// ── NewSession:三个 session,C 与 A 落在同一 worker,验证互不干扰 ───────
|
||||
sessA := gobridge.NewSession(pool) // worker 1
|
||||
sessB := gobridge.NewSession(pool) // worker 0
|
||||
sessC := gobridge.NewSession(pool) // worker 1(与 A 同进程,不同 session_id)
|
||||
|
||||
msgA, err := gobridge.Invoke[string](ctx, sessA, "session_init", "A", 100)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("A init:", msgA)
|
||||
|
||||
msgB, err := gobridge.Invoke[string](ctx, sessB, "session_init", "B", 200)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("B init:", msgB)
|
||||
|
||||
msgC, err := gobridge.Invoke[string](ctx, sessC, "session_init", "C", 300)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("C init:", msgC) // 应与 A 在同一 worker
|
||||
|
||||
// 对 A/B/C 各自做 step,验证三者状态完全独立
|
||||
v, _ := gobridge.Invoke[int](ctx, sessA, "session_step", "A", 10)
|
||||
fmt.Println("A step(+10) =", v) // 110
|
||||
v, _ = gobridge.Invoke[int](ctx, sessA, "session_step", "A", 5)
|
||||
fmt.Println("A step(+5) =", v) // 115
|
||||
|
||||
v, _ = gobridge.Invoke[int](ctx, sessB, "session_step", "B", 50)
|
||||
fmt.Println("B step(+50) =", v) // 250
|
||||
|
||||
v, _ = gobridge.Invoke[int](ctx, sessC, "session_step", "C", 99)
|
||||
fmt.Println("C step(+99) =", v) // 399(与 A 同 worker 但不受影响)
|
||||
|
||||
rA, _ := gobridge.Invoke[map[string]any](ctx, sessA, "session_result", "A")
|
||||
rB, _ := gobridge.Invoke[map[string]any](ctx, sessB, "session_result", "B")
|
||||
rC, _ := gobridge.Invoke[map[string]any](ctx, sessC, "session_result", "C")
|
||||
fmt.Printf("A result = %v\n", rA) // map[steps:[10 5] value:115]
|
||||
fmt.Printf("B result = %v\n", rB) // map[steps:[50] value:250]
|
||||
fmt.Printf("C result = %v\n", rC) // map[steps:[99] value:399]
|
||||
|
||||
// ── 全局变量测试:sessA 和 sessC 同 worker,共享进程级 counter ──────────
|
||||
fmt.Println()
|
||||
r, _ := gobridge.Invoke[string](ctx, sessA, "global_increment", 10)
|
||||
fmt.Println("sessA +10:", r) // worker 1 counter = 10
|
||||
r, _ = gobridge.Invoke[string](ctx, sessC, "global_increment", 5)
|
||||
fmt.Println("sessC +5 :", r) // worker 1 counter = 15(与 sessA 共享)
|
||||
r, _ = gobridge.Invoke[string](ctx, sessB, "global_increment", 99)
|
||||
fmt.Println("sessB +99:", r) // worker 0 counter = 99(独立进程,从 0 开始)
|
||||
r, _ = gobridge.Invoke[string](ctx, sessA, "global_get")
|
||||
fmt.Println("sessA get:", r) // worker 1 counter = 15(不受 sessB 影响)
|
||||
|
||||
// ── WithAffinity:相同 key 跨调用始终路由同一 worker ────────────────────
|
||||
fmt.Println()
|
||||
for i := range 4 {
|
||||
affinityCtx := gobridge.WithAffinity(ctx, "sticky-key")
|
||||
msg, _ := gobridge.Invoke[string](affinityCtx, pool, "session_init",
|
||||
fmt.Sprintf("aff-%d", i), i)
|
||||
fmt.Printf("WithAffinity(sticky-key) #%d → %s\n", i, msg)
|
||||
}
|
||||
|
||||
// ── 对照组:不带亲和,轮询分配给两个 worker ─────────────────────────────
|
||||
fmt.Println()
|
||||
for i := range 4 {
|
||||
msg, _ := gobridge.Invoke[string](ctx, pool, "session_init",
|
||||
fmt.Sprintf("rr-%d", i), i)
|
||||
fmt.Printf("round-robin #%d → %s\n", i, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func demoServer(ctx context.Context, script string) {
|
||||
fmt.Println("\n── Server 全双工示例 ─────────────────────────────────────────────")
|
||||
|
||||
|
||||
@@ -143,6 +143,45 @@ def get_user_via_go(uid: int) -> dict:
|
||||
return dataclasses.asdict(user)
|
||||
|
||||
|
||||
# ── Session 亲和示例 ──────────────────────────────────────────────────────────
|
||||
# _sessions 保存每个 session 的状态,key 由调用方提供
|
||||
_sessions: dict = {}
|
||||
|
||||
# _global_counter 是进程级全局变量,同一 worker 的所有 session 共享
|
||||
_global_counter: int = 0
|
||||
|
||||
|
||||
@expose
|
||||
def global_increment(delta: int) -> str:
|
||||
global _global_counter
|
||||
_global_counter += delta
|
||||
return f"[worker {worker_id}] counter = {_global_counter}"
|
||||
|
||||
|
||||
@expose
|
||||
def global_get() -> str:
|
||||
return f"[worker {worker_id}] counter = {_global_counter}"
|
||||
|
||||
|
||||
@expose
|
||||
def session_init(session_id: str, value: int) -> str:
|
||||
_sessions[session_id] = {"value": value, "steps": []}
|
||||
return f"[worker {worker_id}] session {session_id} init with {value}"
|
||||
|
||||
|
||||
@expose
|
||||
def session_step(session_id: str, delta: int) -> int:
|
||||
s = _sessions[session_id]
|
||||
s["value"] += delta
|
||||
s["steps"].append(delta)
|
||||
return s["value"]
|
||||
|
||||
|
||||
@expose
|
||||
def session_result(session_id: str) -> dict:
|
||||
return _sessions.pop(session_id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
print("worker_id", worker_id)
|
||||
|
||||
Reference in New Issue
Block a user