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.
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.
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
Observer Pattern – Observers listen to state changes and send MQ messages to update indexes, house data, and cache.
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
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.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
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.
