Building a Scalable Coupon System for Rental Platforms: Architecture, DB Design & Concurrency

This article details the end‑to‑end design of a coupon system for a rental listing platform, covering business flow, MySQL table schemas, activity lifecycle management, state‑machine and observer patterns, high‑concurrency claim handling with Redis and MQ, and future scalability optimizations.

21CTO
21CTO
21CTO
Building a Scalable Coupon System for Rental Platforms: Architecture, DB Design & Concurrency

Background

The platform provides rental listings for merchants and consumer‑facing exposure, and now needs a coupon system for holiday marketing where merchants bind coupons to listings to offer price discounts.

1. Business Flow

The overall process is illustrated in the diagram below.

Coupon workflow diagram
Coupon workflow diagram

First, the platform creates an activity; merchants view available activities on the B‑side and sign up, generating coupons. Then listings are bound to coupons, marking them with a coupon flag. On the C‑side, users can filter discounted listings and claim coupons. Finally, after claiming, users contact the merchant for on‑site inspection and sign the lease using the coupon.

2. Technical Design

Each step is designed as follows.

2.1 Create Activity

Data Table

CREATE TABLE `t_activity` (
  `activeId` bigint(20) NOT NULL COMMENT '活动ID',
  `title` varchar(256) NOT NULL COMMENT '活动名称',
  `applyStartTime` timestamp NULL COMMENT '报名开始时间',
  `applyEndTime` timestamp NULL COMMENT '报名停止时间',
  `activityStartTime` timestamp NULL COMMENT '活动开始时间',
  `activityEndTime` timestamp NULL COMMENT '活动结束时间',
  `cityIds` varchar(256) NOT NULL COMMENT '覆盖城市,多个逗号分隔',
  `couponType` tinyint(4) NOT NULL DEFAULT '0' COMMENT '优惠类型,1 直减;2 折扣;3免费住N天;4免押金;5特价房',
  `lowerLimit` int NOT NULL DEFAULT 0 COMMENT '优惠数值下限',
  `upperLimit` int NOT NULL DEFAULT 0 COMMENT '优惠数值上限',
  `description` text COMMENT '活动描述',
  `cubeType` smallint(6) NOT NULL DEFAULT '1001' COMMENT '活动类型',
  `foreignId` bigint(20) NOT NULL DEFAULT '0' COMMENT '外部ID',
  `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '活动状态',
  `createTime` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `updateTime` timestamp NULL DEFAULT NULL COMMENT '更新时间',
  `recordStatus` tinyint(4) NOT NULL DEFAULT '0' COMMENT '数据状态',
  PRIMARY KEY (`activeId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='活动信息表';

Data Retrieval – B‑side filters activities by status and cityIds. C‑side reads use a proxy pattern to add a cache layer.

Activity State Transition – A crontab job checks time windows every minute and updates the status field.

2.2 Merchant Coupon Creation

Data Table

CREATE TABLE `t_couponmeta` (
  `couponMetaId` bigint NOT NULL AUTO_INCREMENT COMMENT '券id',
  `appId` int NOT NULL DEFAULT '1' COMMENT '区分建立来源',
  `activeId` bigint NOT NULL DEFAULT '0' COMMENT '活动ID',
  `companyId` bigint NOT NULL COMMENT '公司编号',
  `cityId` int NOT NULL COMMENT '城市id',
  `companyName` varchar(255) DEFAULT NULL COMMENT '公司名称',
  `companyShortName` varchar(255) DEFAULT NULL COMMENT '公司简称',
  `couponType` tinyint NOT NULL COMMENT '优惠券类型',
  `title` varchar(256) NOT NULL COMMENT '优惠券名称',
  `directDiscount` int NOT NULL DEFAULT '0' COMMENT '直减券优惠力度',
  `discount` int NOT NULL DEFAULT '0' COMMENT '折扣力度',
  `freeLive` int NOT NULL DEFAULT '0' COMMENT '免费住n天券',
  `threshold` varchar(256) NOT NULL COMMENT '使用门槛',
  `deduction` tinyint NOT NULL DEFAULT '1' COMMENT '抵扣说明 1首月抵扣,2 平摊到月',
  `totalAmount` int NOT NULL DEFAULT '0' COMMENT '券总数',
  `applyAmount` int NOT NULL DEFAULT '0' COMMENT '已领取总数',
  `activityStartTime` timestamp NULL COMMENT '活动开始时间',
  `activityEndTime` timestamp NULL COMMENT '活动结束时间',
  `startTime` timestamp NULL COMMENT '券使用开始时间',
  `expireTime` timestamp NULL COMMENT '券使用结束时间',
  `status` int NOT NULL DEFAULT '10' COMMENT '10:新建未启用,20:已启用,30:过期, 40 已结束 50 已中止',
  `expireType` tinyint NOT NULL DEFAULT '1' COMMENT '1固定有效期类型,2浮动有效期类型',
  `validPeriod` tinyint NOT NULL DEFAULT '0' COMMENT '浮动有效期(单位:天)',
  `tenantRange` tinyint NOT NULL DEFAULT '1' COMMENT '租客范围枚举值',
  `customScope` varchar(256) NOT NULL DEFAULT '' COMMENT '自定义租客范围',
  `comment` varchar(50) DEFAULT NULL COMMENT '备注',
  `cubeType` smallint NOT NULL DEFAULT '1001' COMMENT '活动类型',
  `updateTime` timestamp NULL DEFAULT NULL COMMENT '更新时间',
  `createTime` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `recordStatus` tinyint NOT NULL DEFAULT '0' COMMENT '状态 0默认 -1删除',
  PRIMARY KEY (`couponMetaId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='优惠券表';

Data Retrieval – B‑side queries by status and cityIds. C‑side also uses a proxy cache layer.

Coupon State Transition – A crontab updates status and MQ messages notify observers.

State Machine Diagram

Coupon state machine
Coupon state machine

Observer Pattern – Observers listen to state changes and send MQ messages to update indexes, house data, and cache.

Observer pattern
Observer pattern

2.3 Bind Coupons to Listings

Data Table

CREATE TABLE `t_bindcoupon` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
  `couponMetaId` int NOT NULL COMMENT '券id',
  `companyId` bigint NOT NULL COMMENT '公司编号',
  `activityStatus` tinyint NOT NULL COMMENT '状态 0 准备中 1 活动中 2 活动结束 券未失效 3活动结束券失效',
  `houseId` bigint NOT NULL DEFAULT '0' COMMENT '房源id',
  `recordStatus` tinyint NOT NULL COMMENT '数据状态 0 有效,-1 失效',
  `createTime` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `updateTime` timestamp NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_companyId_couponMetaId` (`companyId`,`couponMetaId`),
  KEY `idx_houseid_activitystatus` (`houseId`,`activityStatus`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='优惠券绑定范围表';

The table records which listings are bound to which coupons and enforces limits on the number of active coupons per listing.

Operations use a distributed lock to avoid concurrent over‑binding.

Data Presentation – C‑side indexes store only active coupon info; fields such as coupon IDs, activity IDs, and activity types are used for filtering.

2.4 C‑Side Coupon Claim

The claim process must prevent over‑claiming. The flow includes request validation, Redis stock decrement, and a transactional MySQL record update.

Data Reading – All data (activity info, coupon info, stock) is cached in Redis; a short‑lived local cache further reduces Redis load.

Validation – A Bloom filter in Redis checks if a user has already claimed; coupon status is verified from Redis; missing stock triggers an async initialization task.

Stock Decrement

Redis succeeds but DB write fails → rollback Redis stock.

Redis fails (no stock or Redis down) → no DB operation.

Redis succeeds, DB transaction crashes → possible under‑claim.

Redis succeeds, DB writes to replica only → possible over‑claim.

Cases 1‑2 are normal; 3‑4 are abnormal. Mitigations include async reconciliation tasks, stock‑freeze when remaining stock drops below a threshold, and using CP‑style storage for strong consistency.

Several strategies were considered; the chosen approach combines periodic stock‑freeze and alerting, with occasional manual cleanup.

Feedback and better solutions are welcome.

2.5 Coupon Redemption

Micro‑service APIs provide coupon acquisition and state‑change endpoints. States transition among "unused", "locked", and "used". When an order is placed, the coupon is locked; on order completion it becomes used, otherwise it rolls back to unused.

Future Optimization Directions

Shard user coupon data across databases for higher read/write throughput.

Deploy Redis cluster with sharding for better availability and capacity.

Split service clusters so each handles a subset of coupons, routing via a gateway.

Introduce hot‑key components to sync frequently accessed keys to local caches.

Use Canal to sync binlog changes to cache and trigger downstream updates.

Conclusion

The complete coupon system follows the principle of "read‑heavy use cache, write‑heavy use queue". Cache‑first reads use proxy pattern; writes employ state‑machine, observer pattern, and MQ to serialize updates. Redis provides atomic stock decrement, while DB transaction messages ensure eventual consistency and prevent over‑claiming.

Thank you for reading – hope this helps! Source: juejin.cn/post/7160643319612047367

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

backend designhigh concurrencyCoupon Systemdatabase schema
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.