Skip to content

feat(divide): add optional sticky session affinity composed with load balancing (#8037) - #8098

Merged
Aias00 merged 1 commit into
apache:masterfrom
konlue:feat/divide-sticky-session-affinity
Oct 26, 2026
Merged

Aias00 merged 1 commit into
apache:masterfrom
konlue:feat/divide-sticky-session-affinity

Conversation

@konlue

@konlue konlue commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Implements the first delivery phase of #8037: optional HTTP session affinity for Divide, composed with — never replacing — the configured load-balancing algorithm.

Summary

For a new session the base algorithm selects the target. For an existing binding, the affinity layer reuses the bound target — but only while it is still a member of the candidate set that has already passed canary partitioning and circuit-breaker admission, so a binding can never override namespace boundaries, label partitions, health or breaker admission.

Modifications

  • StickySessionConfig (rule handle, nested like the canary config): enabled, cookieName, maxAgeSeconds, secrets and onUnavailable (REBIND default / STRICT).
  • Binding & Signature: The binding travels in a gateway-issued HttpOnly cookie whose value is base64url(ruleId | upstream identity | expiry) plus an HMAC-SHA256 signature.
    • The rule id scopes the binding (a cookie of another rule never binds this one), and the expiry bounds it.
    • Verification accepts every configured key so a key can be rotated by appending it. Comparison is constant-time.
  • DividePlugin Resolution:
    • Resolves the binding after canary/breaker filtering: a hit selects the bound target directly (the same path an explicit specify-domain uses, so all attempt accounting — least-active inflight, P2C lifecycle, breaker keys — applies unchanged).
    • A miss under REBIND selects with the base algorithm and rewrites the cookie.
    • A miss under STRICT rejects the request with 503 instead of silently moving the session.
    • The cookie is written before the response headers are committed, so a downstream retry to another target never rewrites the binding.
  • Overrides & Defaults: An explicit specify-domain request takes precedence over affinity and is never bound. Disabled rules keep the plain load-balancing behavior: no cookie, no allocation.

Verifying this change

  • New session writes a signed cookie (HttpOnly / Path / Max-Age / SameSite) that resolves back to the selected target.

  • A valid binding pins two consecutive requests to the bound target under round-robin.

  • A bound target that is removed, or blocked by the circuit breaker, is not honored: REBIND re-selects inside the remaining candidates and rewrites the cookie.

  • STRICT rejects with 503 when the bound target is unavailable; tampered, expired and foreign-rule cookies are treated as a miss; a cookie signed with a previous key still verifies.

  • specify-domain overrides affinity; default-off rules keep plain behavior with no Set-Cookie; least-active accounting returns to its baseline on a binding hit.

  • STRICT does not affect cookie-less new sessions: they are still allocated by the base algorithm; broken configurations (blank secrets, non-positive max age) behave as disabled.

  • A P2C binding hit reserves the attempt and refreshes the EWMA timestamp like a P2C selection; the accounting returns to baseline on completion.

  • The binding key is the upstream identity: two upstreams sharing a URL but differing in group do not absorb each other's binding; HTTPS requests get a Secure cookie.

  • All related tests and checkstyle pass:

    shenyu-common: 120
    shenyu-plugin-divide: 139 (incl. 15 new sticky-session tests)
    checkstyle: clean
    

Out of scope (later phases per the issue)

Redis/distributed binding stores, deterministic-key distribution benchmarks, admin-side validation UI, and protocols other than HTTP.

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. The sticky session affinity feature is cleanly designed and securely implemented: the binding travels in a gateway-issued, HMAC-SHA256-signed cookie (base64url payload.signature of ruleId|hashKey|expiry), verification walks all configured secrets so keys can be rotated, and isActive() fails safe when secrets/cookieName are blank or maxAge is non-positive. A binding is only honored when its target is still a member of the already-admitted (canary/health/breaker) candidate list, and STRICT mode rejects (503) only when a valid binding exists but its target is ineligible - a request without a binding is a new session and is allocated normally. The P2C inflight accounting is correctly paired on a binding hit, and the cookie is written after target selection but before response commit so a downstream retry never rewrites the binding. Equals/hashCode/toString on DivideRuleHandle were updated for the new sticky field. Nice work.

@Aias00
Aias00 merged commit b940b2c into apache:master Oct 26, 2026
51 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants