Designing a Shopping‑Cart Service for Ten‑Million Users
This article explains how to architect a high‑traffic e‑commerce shopping‑cart system that serves tens of millions of users by using a process‑chain engine, Redis Cluster as the primary store, asynchronous persistence to MySQL, and a modular design that supports multiple cart types while ensuring consistency and scalability.
When a shopping‑cart service grows from a simple CRUD component to a system handling millions of users, the implementation quickly becomes far more complex. Adding a product may involve inventory checks, promotion rules, packaging fees, calls to a settlement service, and finally persisting the cart, resulting in a dozen steps that can be parallel or sequential, and that may succeed or fail independently.
Global Overview
User actions from a mini‑program or app are routed through a gateway to the cart service. The core logic is driven by a process‑chain engine, the main data lives in a Redis Cluster, and changes are asynchronously persisted to MySQL via a message queue (MQ). Price calculation is delegated to a separate settlement service accessed via RPC, while product, inventory, and promotion data are fetched from dedicated product and marketing services.
Why Not a Monolithic Service Method?
A single Service method would have to handle at least three problems: (1) heavy step overlap between operations such as add‑to‑cart and select‑item, (2) opportunities for parallel execution (e.g., loading related items and cleaning the cart can run concurrently), and (3) the need to return to the client before persistence finishes. A process‑chain engine solves these by enabling step reuse, parallelism, and mixed sync‑async execution.
Process‑Chain Core Abstraction
The engine defines two concepts: ProcessNode, which encapsulates a single step, and ProcessChain, which orders nodes and specifies whether each node runs synchronously ( sync()) or asynchronously ( async(id, node)). An example chain for an add‑to‑cart operation shows a mix of sync and async calls, with the final persistence step marked as asynchronous and non‑blocking.
Operation‑Specific Chains
Four core cart operations (add, select, detail, checkout) share many steps but differ in a few. About 60 % of steps are reused across operations, justifying the engine approach. The longest chain (add) has 15 steps; the detail chain adds more parallel reads because it does not modify data.
Redis as Primary Store
For a ten‑million‑user scenario, Redis is chosen over MySQL for three reasons: (1) write‑heavy workload (tens of thousands of QPS during peaks), (2) natural fit of cart data to Redis Hashes (O(1) HGET/HSET), and (3) built‑in TTL for expiring inactive carts. A typical key layout is cart:user:{userId} with fields for different product types and metadata such as version and timestamp.
Choosing Redis Cluster
Estimating 30 % active users yields roughly 3 million cart hashes (~5 KB each), requiring 15–30 GB of memory after overhead. A single master cannot hold this amount, and RDB snapshots would double memory usage, risking OOM. Cluster mode provides sharding, linear write scalability, and automatic failover, making it the right choice for ten‑million‑level traffic.
Asynchronous Persistence Options
Three approaches are compared: (1) real‑time MQ (seconds latency, moderate complexity), (2) periodic scan (minutes latency, simple), and (3) a hybrid MQ + timer fallback (seconds latency, highest reliability). The hybrid solution is adopted: normal flow uses MQ for near‑real‑time persistence, while a timer task rescans Redis‑MySQL version mismatches every five minutes to recover from MQ failures.
Write‑Merge Strategies
Because a cart only needs to persist its final state, intermediate updates can be merged. Three strategies are evaluated: delayed‑message merging (5 s delay), dirty‑flag with periodic batch, and in‑process batch‑submit. The delayed‑message approach is selected: after a successful Redis write, a 5‑second delayed MQ message is sent if no such message exists, ensuring only the latest state is written to MySQL.
Degradation Plans
If the Redis Cluster fails, the service falls back to direct MySQL reads/writes (reduced QPS but no outage). If MQ fails, Redis continues serving requests while the timer task eventually syncs data. If MySQL fails, user requests still hit Redis; persistence pauses and resumes once MySQL recovers.
Supporting Multiple Cart Types
The system serves personal carts, group‑order carts, and corporate catering carts. Each type uses a distinct Redis key pattern and TTL (e.g., cart:spell:{roomId}:{userId} for group orders with a 1‑day TTL) but shares the same process‑chain infrastructure. Adding a new cart type only requires defining its specific nodes and key pattern.
Collaboration with the Settlement Center
The cart service never calculates prices; it packages the cart items and user info and calls the settlement center via RPC to obtain final prices and discounts. This separation keeps cart logic lightweight and isolates complex pricing rules. If the settlement service is unavailable, the cart returns the original price with a “price loading” flag.
Key Architectural Judgments
Cart data is whole‑record, write‑once‑replace, allowing eventual consistency and delayed writes.
Process‑chain engine is worthwhile when an operation exceeds eight steps and more than half of the steps are shared across operations.
Storage should match data volume; Redis Cluster is chosen only because a single master cannot handle the required memory and QPS.
In summary, the architecture demonstrates how to keep a seemingly simple cart interface manageable at massive scale by abstracting workflow, choosing the right storage tier, and designing resilient async persistence.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
samdeepthink
Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
