计数系统设计
在营销的场景里有三要素
- 用户
- 商品
- 优惠
在这三个要素里,再加一些如时间,数量,频次等变量,会演化出各种组合,使得业务变得非常灵活。各业务线为了满足业务,一般都会各自实现,且多数情况下都会重复实现,而且实现起来各地方都会产生交叉配置,交叉互斥的问题。在观察到这些问题后,总结并尝试用一个计数频次中间件形态的系统统一解决。一是解决了复杂度、重复建设;二是统一处理后,数据层面上将整体打通,对于用户监控、风控、特征、行为上提供完整数据。
在电商的系统中,有商品、用户、优惠三种要素。这三种要素分别在数量,时间,频次上又有多维度的分类。
如商品限量100份(数量)对用户每天(时间)每人购买限量2份(频次),按这种规律下,用户使用优惠也同样遵循这种规则,即
优惠券总数100张(数量),用户每场(时间)可使用1张(频次)
抽奖优惠也一样,即
优惠总数100张,用户每天(时间)可领取1张(频次)
发放优惠也一样,即
优惠总额100元,用户每天(时间)可优惠1次(频次)
分类法
- 按主体,维度分类
主体\维度 | 数量 | 时间 | 频次 |
---|---|---|---|
商品 | 总数 | 指定周期,天,小时,周 | 不限,1份,多份 |
用户 | 总数 | 指定周期,天,小时,周 | 不限,1份,多份 |
优惠 | 总数 | 指定周期,天,小时,周 | 不限,1份,多份 |
活动 | 总数 | 指定周期,天,小时,周 | 不限,1份,多份 |
按上面这个分类,主体会变,维度会变,先设计领域模型,分别为实体(entity);维度(dimensions);实体维度关系(entityDimensionsRel),关系领域将表示,主体将拥有多个维度,并在多个维度中设定维度值,如商品每天限购1份,那么将在entityDimensionsRel中的limit_value中设定1,time_interval设定为1天
领域模型
数据模型
数据库表模型
- 主体表 (Entities)
- EntityID: 主键,唯一标识某个主体(如商品、优惠、活动等)
- EntityType: 主体类型(如商品、优惠、抽奖等)
- EntityName: 主体名称
CREATE TABLE Entities (
EntityID INT PRIMARY KEY,
EntityType VARCHAR(50),
EntityName VARCHAR(100)
);
- 维度定义表 (Dimensions)
- DimensionID: 主键,唯一标识某个维度(如数量、时间、频次)
- DimensionName: 维度名称(如“数量”、“时间”、“频次”)
- DimensionType: 维度类型(如整数、日期、时间区间等)
CREATE TABLE Dimensions (
DimensionID INT PRIMARY KEY,
DimensionName VARCHAR(50),
DimensionType VARCHAR(50)
);
- 主体-维度关系表 (EntityDimensions)
- EntityDimensionID: 主键,唯一标识某个主体-维度关系
- EntityID: 外键,关联到Entities表
- DimensionID: 外键,关联到Dimensions表
- LimitValue: 该维度的限制值(如限量、限次等)
- TimeInterval: 时间间隔(如每天,每场活动等),适用于时间维度
CREATE TABLE EntityDimensions (
EntityDimensionID INT PRIMARY KEY,
EntityID INT,
DimensionID INT,
LimitValue INT,
TimeInterval VARCHAR(50),
FOREIGN KEY (EntityID) REFERENCES Entities(EntityID),
FOREIGN KEY (DimensionID) REFERENCES Dimensions(DimensionID)
);
上面这些表可以完整记录主体在各维度的限制值。还需要一张实时记录主体数据在各维度的过程数据
- 实例数据记录表 (EntityDimensionRecords)
- RecordID: 主键,唯一标识某条记录
- EntityID: 外键,关联到Entities表
- DimensionID: 外键,关联到Dimensions表
- UserID: 外键,关联到用户表(适用时)
- Value: 在该维度上记录的数值
- RecordDate: 记录的日期或时间。
CREATE TABLE EntityDimensionRecords (
RecordID INT PRIMARY KEY,
EntityID INT,
DimensionID INT,
UserID INT,
Value INT,
RecordDate DATETIME,
FOREIGN KEY (EntityID) REFERENCES Entities(EntityID),
FOREIGN KEY (DimensionID) REFERENCES Dimensions(DimensionID)
);
数据流转
样例1
用户参与大转盘活动,活动周期内只能抽一次奖
主体:大转盘活动
维度:时间,计数
关系:限制值(一次);限制时间(活动周期);
简单填入数据,业务就可以满足了。
现在又有新的业务需求,即这个活动周期为1个月,用户每天都要能参与一次,现有的表模型就不支持了!
关系表(EntityDimensions)的时间字段TimeInterval是个varchar,表示1天、1小时等枚举值是可以,如果表示时间段,每天等带频次的复杂时间是不够的,所以,最好的办法之一就是将时间维度拆出一张表
CREATE TABLE TimePeriods (
TimePeriodID INT PRIMARY KEY,
PeriodName VARCHAR(50),
TimeInterval VARCHAR(50), -- 时间周期(每天、每小时、每半小时、每分钟)
StartTime TIME, -- 时间段开始时间
EndTime TIME -- 时间段结束时间
);
有了这张表后,就可以表示周期时间段的计数关系了。
有了这张表后,业务在查询EntityDimensionRecords,就知道这条记录的周期,开始时间结束时间。
如果时间频次TimeInterval是天,当前时间为2022-12-12时,EntityDimensionRecords.RecordDate值为2022-12-11时,那么EntityDimensionRecords需要重新生成一条记录,并且RecordDate值为2022-12-12;
越往深入,能够枚举出的维度是有限的,在大的方向上,每一个维度也可以拆分出一个领域也是可行的。因为维度值不一样,单纯用维度一个变量有点满足不了需求,如时间维度和数量维度,维度值用一个变量就表示不了,时间范围最少需要2个字段。所以在不同的业务发展时期,只有当时最合适的设计,没有永远对的设计。在考虑扩展性的同时,也需要考虑研发成本。
如果以后有需求对计数的方式有变化,比如一次计数消耗2次机会,那相应的,再在EntityDimensions.LimitValue字段上做一个新表来表示计数的复杂方式。万变不离其宗,好的设计一定是支持业务慢慢成长起来的。糟糕的设计是不停的妥协设计,随着时间推移开发和维护将会越来越困难!