High‑Concurrency Order System Architecture: How Redis, MySQL, and Elasticsearch Collaborate Without Overstepping
This article presents a production‑grade, high‑concurrency order system design that separates responsibilities among Redis for traffic control, MySQL as the single source of truth, Kafka for event propagation, and Elasticsearch for search, while detailing state‑machine modeling, outbox patterns, seckill flow, and comprehensive observability and deployment practices.
Problem Overview
Many teams start a high‑traffic order system by simply adding Redis, Elasticsearch, and MySQL, assuming the challenge is only to "connect the three middlewares". The real difficulty lies in four core questions: where the order truth resides, how peak traffic is admitted without overwhelming the database, how to achieve final consistency among order, payment, inventory, and search, and how to recover from failures.
Business Attributes
Strong state : every order passes states such as pending payment, paid, cancelled, shipped, completed and each transition must be legal.
Strong amount : discounts, actual payment, refunds, points, and balances must be exact; any mis‑calculation is a financial incident.
Strong linkage : an order ties inventory, payment, fulfillment, marketing, risk control, customer service, search, and reporting.
The architecture must therefore be driven by transaction truth + event propagation + read‑model separation + traffic governance , not by naive caching.
Component Responsibilities
Redis – Admission Control, Hotspot Cache, Short‑Lived State
Token‑bucket or inventory pre‑lock for seckill qualification.
User‑level rate limiting and idempotency keys.
Short‑term order‑detail cache.
MySQL – Order Truth Source & Transaction Boundary
Persist the primary order record, order items, amount snapshots, and unique constraints.
Store the outbox event table in the same transaction.
Never use MySQL for fuzzy search or heavy aggregation.
Elasticsearch – Search Projection
Only a read‑model for user and operation queries; it does not hold the authoritative order state.
Designed with keyword and text fields, analyzers, and appropriate sharding.
Boundary‑Driven Architecture
The system is split into five logical planes:
Access plane : API gateway authentication, user‑level rate limiting, blacklist, idempotency guard, activity qualification, and circuit breaking.
State plane : a database‑driven state machine with explicit expectedStatus checks to guarantee legal transitions.
Event plane : local outbox events are written in the same transaction and later relayed to Kafka.
Read‑model plane : Elasticsearch indexes are updated asynchronously from the event stream.
Governance plane : observability, consistency inspection, message backlog handling, dead‑letter recovery, capacity testing, and safe rollout.
State Machine Implementation
UPDATE t_order
SET status = :targetStatus,
version = version + 1,
update_time = NOW(3)
WHERE order_id = :orderId
AND status = :expectedStatus
AND is_deleted = 0;The expectedStatus column is the core guard that prevents illegal state jumps.
Outbox Pattern
Order creation writes both the order rows and an outbox_event row inside the same transaction. A scheduled job picks pending events, sends them to Kafka, and updates their status.
@Scheduled(fixedDelay = 1000)
public void relay() {
List<OutboxEvent> events = repo.lockPendingEvents(500);
for (OutboxEvent e : events) {
try {
kafkaTemplate.send(e.topic(), e.partitionKey(), e.payload()).get(3, SECONDS);
repo.markSent(e.getEventId());
} catch (Exception ex) {
repo.markRetry(e.getEventId(), e.getRetryCount() + 1, nextRetryTime(e.getRetryCount()));
}
}
}Seckill (Flash‑Sale) Flow
During a promotion, stock is pre‑loaded into Redis. The gateway validates signatures, rate limits, and blacklist. A Lua script atomically checks stock and whether the user has already bought, then decrements the token.
-- keys[1] = stock key
-- keys[2] = user bought set
-- argv[1] = user id
local stock = tonumber(redis.call('get', KEYS[1]) or '-1')
if stock <= 0 then return {err='NO_STOCK'} end
if redis.call('sismember', KEYS[2], ARGV[1]) == 1 then return {err='ALREADY_BOUGHT'} end
redis.call('decr', KEYS[1])
redis.call('sadd', KEYS[2], ARGV[1])
return {ok='OK'}Successful requests write a seckill‑order‑create event to Kafka; a consumer builds the final MySQL order and updates the stock token on failure.
Payment Callback Handling
Callbacks are idempotent and must advance the order from PENDING_PAYMENT to PAID only once. The service locks the payment row, checks if it is already paid, updates the order status with an expected_status guard, records the payment details, and emits an orderPaid outbox event.
int updated = orderRepo.updateStatusWithExpectation(
orderId,
OrderStatus.PENDING_PAYMENT,
OrderStatus.PAID);
if (updated == 0) {
// already PAID or illegal transition
throw new BizException(...);
}Schema Highlights
CREATE TABLE t_order (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT NOT NULL UNIQUE,
user_id BIGINT NOT NULL,
shop_id BIGINT NOT NULL,
order_status TINYINT NOT NULL,
total_amount BIGINT NOT NULL,
discount_amount BIGINT NOT NULL DEFAULT 0,
pay_amount BIGINT NOT NULL,
currency CHAR(3) NOT NULL DEFAULT 'CNY',
source VARCHAR(32) NOT NULL,
idempotent_no VARCHAR(64) NOT NULL,
version INT NOT NULL DEFAULT 0,
ext_json JSON,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
UNIQUE KEY uk_user_idempotent (user_id, idempotent_no),
KEY idx_user_status_ctime (user_id, order_status, create_time),
KEY idx_shop_ctime (shop_id, create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT 'order main table';Observability & Governance
Four metric groups are required:
Interface & traffic: QPS, p99 latency, error rate, gateway rate‑limit count.
State correctness: pending‑payment count, duplicate payment callbacks, illegal transition count.
Event & sync: outbox pending/retry counts, Kafka lag, projection delay, dead‑letter volume.
Resource & capacity: MySQL connections, Redis hit ratio, ES bulk latency, JVM GC pauses, pod restarts.
Alerts fire when projection delay exceeds SLA, outbox backlog grows, or dead‑letter queues accumulate.
Testing & Release Practices
Load tests must cover mixed traffic: normal orders, seckill admission, repeated payment callbacks, hotspot detail queries, and operation‑side complex searches. Success criteria include admission success rate, order success rate, and the ability to drain a 5‑minute message backlog within 10 minutes.
Deployments use stateless multi‑replica services, readiness probes after dependencies, preStop traffic drain, and separate consumer services. Database DDL and ES index changes follow gray‑release and alias‑swap strategies, and event schemas remain backward compatible.
Evolution Roadmap
Single‑instance MySQL with minimal caching.
Sharded MySQL + Redis for hot keys + Kafka + Elasticsearch projection.
Activity‑level traffic governance, unit‑based isolation, and stronger event isolation.
The final takeaway is three iron rules: a single source of truth (MySQL), event‑driven cross‑system coordination, and traffic‑first governance before transaction optimization.
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.
Cloud Architecture
Focuses on cloud‑native and distributed architecture engineering, sharing practical solutions and lessons learned. Covers microservice governance, Kubernetes, observability, and stability engineering to help your systems run stable, fast, and cost‑effectively.
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.
